var WidgetService = {};

WidgetService.GetWidgets = function (onSuccess, onError)
{
    var filterInitContainer = $("#filter-init");

    var url = filterInitContainer.length == 0 ? '/Services/Widgets/WidgetsService.svc/' : filterInitContainer.attr('data-widgetservice-url');

    $.ajax({
        type: 'GET',
        url: url,
        success: onSuccess,
        error: onError
    });
};var PropertyAccessor = function ()
{
    var _log = log4javascriptFactory.getLogger('property accessor');
    _log.setLevel(log4javascript.Level.WARN);

    this.GetProperty = function (propertyName)
    {
        if (propertyName == null)
            throw new Error('propertyName cannot be null');

        if (!this.hasOwnProperty(propertyName))
        {
            _log.error('Property [' + propertyName + '] is not defined on current object', this);
            throw new Error('Property [' + propertyName + '] is not defined on current object');
        }

        return this[propertyName];
    }
}

var WidgetsEnvironmentClass = function (data, listFilters)
{
    var _log = log4javascriptFactory.getLogger('widgets environment');
    _log.setLevel(log4javascript.Level.WARN);

    _log.trace('initializing environment');

    if (data == null)
    {
        var pageData = $('#widget-init');

        if (pageData.length == 0)
            throw new Error('Data is not specified explicitly and page does not contain widgets data');

        data = pageData[0];
    }

    if (listFilters == null)
    {
        var pageListFilters = $('#filter-init');

        if (pageListFilters.length == 0)
            throw new Error('List filters are not specified explicitly and page does not contain list filters');

        listFilters = pageListFilters[0];
    }

    var self = this;

    var buildProperty = function (dataContainer)
    {
        if (dataContainer == null)
            throw new Error('dataContainer cannot be null');

        var result = new PropertyAccessor();
        var dataAttributes = $(dataContainer).dataset();

        $(Object.getPropertyNames(dataAttributes)).each(function ()
        {
            result[this] = dataAttributes[this];
        })

        return result;
    }

    var addProperty = function (dest, dataContainer)
    {
        if (dest == null)
            throw new Error('dest cannot be null');

        if (dataContainer == null)
            throw new Error('dataContainer cannot be null');

        var propertyName = $(dataContainer).attr('id');

        if (propertyName == null || propertyName == '')
            throw new Error('invalid property name on node [' + dataContainer.outerHTML + ']');

        var propertyValue = buildProperty(dataContainer);

        dest[propertyName] = propertyValue;
    }

    // analyze data and assign corresponding properties
    $($(data).children()).each(function () { addProperty(self, this); });

    // build list filters
    var listFiltersArray = [];
    this['list-filters'] = listFiltersArray;

    $($(listFilters).children()).each(function () { listFiltersArray.push(buildProperty(this)) });

    this.widetsAllowed = $(data).attr('data-widgets-allowed') == 'true';
}

WidgetsEnvironmentClass.prototype = new PropertyAccessor();

/// <summary>Gets the number of properties on an object</summary>
Object.getPropertyNames = function (obj) {
    if (obj == null)
        throw new Error("obj cannot be null");
    var properties = new Array();
    for (key in obj) {
        properties.push(key);
    }
    return properties;
};;/* Takes care of retrieving widgets from server */
var WidgetsDatabase = function (widgets, onReadyCallback)
{
    var _log = log4javascriptFactory.getLogger('widget database');
    _log.setLevel(log4javascript.Level.WARN);

    _log.trace('creating widgets database');

    // widgets array
    var _widgets = [];

    var createFilter = function (widget, filterProperty, appliesTo, property, filterContainer)
    {
        if (widget == null)
            throw new Error('widget cannot be null');

        if (filterProperty == null)
            throw new Error('filterProperty cannot be null');

        if (appliesTo == null)
            throw new Error('appliesTo cannot be null');

        if (property == null)
            throw new Error('property cannot be null');

        if (filterContainer == null)
            throw new Error('filterContainer cannot be null');

        // check if filter value is null
        var value = $('.filter', widget).attr(filterProperty);
        if (value == null || value == "")
            return; // filter is not specified, ignore it

        filterContainer.push(new WidgetsDatabase.WidgetFilter(appliesTo, property, '==', value, 1));
    }

    var createEquasionOperator = function (serverOperator)
    {
        if (serverOperator == null)
            throw new Error('serverOperator cannot be null');

        switch (parseInt(serverOperator))
        {
            case 0:
                return '==';

            case 1:
                return '!=';

            case 2:
                return '>';

            case 3:
                return '>=';

            case 4:
                return '<';

            case 5:
                return '<=';

            default:
                throw new Error('Unknown server-side operator: [' + serverOperator + ']');
        }
    }

    var parseDimension = function (dimension)
    {
        if (dimension == null)
            throw new Error('dimension cannot be null');

        //    public enum DimensionEnum
        //    {
        //        Category = 0,
        //        Collection = 1,
        //        Color = 2,
        //        SubCategory = 3,
        //        ReleaseDate = 4,
        //        Metal = 5,
        //        Material = 6,
        //        Stone = 7
        //    }

        switch (parseInt(dimension))
        {
            case 0:
                return 'cat';

            case 1:
                return 'col';

            case 2:
                return 'color';

            case 3:
                return 'subcat';

            case 4:
                return 'release';

            case 5:
                return 'metal';

            case 6:
                return 'material';

            case 7:
                return 'stone';

            default:
                throw new Error('Unknown dimension: [' + dimension + ']');
        }
    }

    var createProductFilters = function (widget, filterProperty, filterContainer)
    {
        var filtersGroup = [];

        var filters = $('.filter li', widget);
        // check if filter count is zero
        if (filters.length < 1)
            return;

        var filtersGroup = [];

        $(filters).each(function ()
        {
            filtersGroup.push(new WidgetsDatabase.WidgetFilter('list-filters', parseDimension($(this).attr('dimension')), createEquasionOperator($(this).attr('operator')), $(this).attr('keys').split(';'), 2));
        });

        filterContainer.push(new WidgetsDatabase.WidgetFilterGroup(filtersGroup));
    }

    var createWidget = function (widget)
    {
        // get basic widget information
        var result = new WidgetsDatabase.Widget(widget.attr('widgetid'), widget.attr('width'), widget.attr('height'), $('.htmlcode', widget).html(), []);

        // get filters
        $(['Gender', 'HasDesign', 'HasFavouriteStore', 'HasWishlist', 'IsEcommerce', 'IsMember', 'IsSubscribed', 'ProductFilters'])
        .each(function (index, propertyName)
        {
            switch (propertyName)
            {
                case 'ProductFilters':
                    createProductFilters(widget, propertyName, result.filters);
                    break;

                case 'Gender':
                    createFilter(widget, propertyName, 'segment', 'gender', result.filters);
                    break;

                case 'HasDesign':
                    createFilter(widget, propertyName, 'segment', 'has-design', result.filters);
                    break;

                case 'HasFavouriteStore':
                    createFilter(widget, propertyName, 'segment', 'has-favourite-store', result.filters);
                    break;

                case 'HasWishlist':
                    createFilter(widget, propertyName, 'segment', 'has-wishlist', result.filters);
                    break;

                case 'IsEcommerce':
                    createFilter(widget, propertyName, 'market', 'is-ecommerce', result.filters);
                    break;

                case 'IsMember':
                    createFilter(widget, propertyName, 'segment', 'is-member', result.filters);
                    break;

                case 'IsSubscribed':
                    createFilter(widget, propertyName, 'segment', 'is-subscribed', result.filters);
                    break;

                default:
                    throw new Error('Widget property is not recognized: [' + propertyName + ']');
            }
        })

        return result;
    }

    var initWidgets = function ()
    {
        _log.trace('initializing widgets');

        var done = function ()
        {
            _log.trace('initialization done, ' + _widgets.length + ' widget(s) are in database');

            if (onReadyCallback)
                onReadyCallback();
        }

        if (widgets != null)
        {
            _widgets = widgets;
            done();
        }
        else {
            setTimeout(function () {
                $('#widget-collection .widget-control').each(function() {
                    _widgets.push(createWidget($(this)));
                    $('#widget-collection').remove();
                });
                done();
            }, 0);
        }
    }

    /* Returns available widgets */
    this.GetWidgets = function ()
    {
        // return a copy
        return _widgets.slice(0);
    }

    initWidgets();
}

WidgetsDatabase.Widget = function (id, width, height, html, filters)
{
    if (id == null)
        throw new Error('id cannot be null or empty');

    if (width == null)
        throw new Error('width cannot be null or empty');

    if (height == null)
        throw new Error('height cannot be null or empty');

    if (html == null)
        throw new Error('html cannot be null or empty');

    if (filters == null)
        throw new Error('filters cannot be null or empty');

    this.id = id;
    this.width = parseInt(width);
    this.height = parseInt(height);
    this.filters = filters;
    this.html = htmlDecode(html);
}

WidgetsDatabase.WidgetFilter = function (appliesTo, property, condition, value, weight)
{
    if (appliesTo == null)
        throw new Error('appliesTo cannot be null or empty');

    if (property == null)
        throw new Error('property cannot be null or empty');

    if (condition == null)
        throw new Error('condition cannot be null or empty');

    if (value == null)
        throw new Error('value cannot be null or empty');

    if (weight == null || weight < 0)
        weight = 0;

    this.appliesTo = appliesTo;
    this.property = property;
    this.condition = condition;
    this.value = value;

    this.getWeight = function ()
    {
        return weight;
    }
}

WidgetsDatabase.WidgetFilterGroup = function (filters)
{
    if (filters == null)
        throw new Error('filters cannot be null or empty');

    this.filters = filters;

    this.getWeight = function ()
    {
        return this.filters.length == 0 ? 0 : $.Enumerable.From(this.filters).Sum('$.getWeight()');
    }
};/* Provides widgets for JS client */
var WidgetsManagerClass = function (widgetsDatabase, onReadyCallback)
{
    var _log = log4javascriptFactory.getLogger('widget manager');
    _log.setLevel(log4javascript.Level.WARN);

    _log.trace('creating widget manager');

    var self = this;
    this.name = "WidgetsManagerClass";

    this.onLoadEvent = new pandora.utils.Event(this);

    /* Performs manager initialization */
    var initManager = function() {
        if (onReadyCallback)
            onReadyCallback(self);
    };

    /* Returns widgets */
    this.GetWidgets = function (environment, userFilters)
    {
        _log.trace('retrieving widgets');

        if (!environment.widetsAllowed)
        {
            this.onLoadEvent.notify({ widgets: [] });
            return [];
        }

        var environmentCopy = $.extend(true, {}, environment);

        if (userFilters != null)
        {
            for (var filter in userFilters)
            {
                if (userFilters.hasOwnProperty(filter))
                {
                    var values = [];

                    if (userFilters[filter].length < 1)
                        throw new Error('Invalid user filters length');

                    var comparison = userFilters[filter][0].comparison;

                    for (var i = 0; i < userFilters[filter].length; i++)
                    {
                        values.push(userFilters[filter][i].value);
                    }

                    environmentCopy['list-filters'].push({
                        'filter-field': filter,
                        'filter-compare': comparison,
                        'filter-value': values
                    })
                }
            }
        }

        var availableWidgets = _widgetsDatabase.GetWidgets();
        var widgetsWeights = {};

        var result = $.Enumerable.From(availableWidgets)
            .Where(function (widget)
            {
                widgetsWeights[widget.id] = 0;

                return $.Enumerable.From(widget.filters)
                    .All(function (filter)
                    {
                        var result = false;

                        if (filter instanceof WidgetsDatabase.WidgetFilterGroup)
                        {
                            if (filter.filters.length == 0)
                                result = true;
                            else
                                $(filter.filters).each(function (index, filter)
                                {
                                    var filterResult = WidgetsManagerClass.IsFilterOk(filter, environmentCopy);

                                    if (filterResult)
                                    {
                                        result = true;
                                        widgetsWeights[widget.id] += filter.getWeight();
                                    }
                                })
                        }
                        else
                        {
                            result = WidgetsManagerClass.IsFilterOk(filter, environmentCopy);

                            if (result)
                                widgetsWeights[widget.id] += filter.getWeight();
                        }

                        return result;
                    });
            })
            .OrderByDescending(function (widget)
            {
                return widgetsWeights[widget.id];
            })
            .ToArray();

        _log.info(result.length + ' widgets are returned');
        /*
        $(result).each(function ()
        {
        _log.info(this);

        $(this.filters).each(function ()
        {
        _log.info(this);
        })
        });
        */
        //        $(result).each(function ()
        //        {
        //            _log.debug(this);
        //        })

        this.onLoadEvent.notify({ widgets: result });

        return result;
    }

    // widgets database
    var _widgetsDatabase = widgetsDatabase || new WidgetsDatabase(null, initManager);
}

WidgetsManagerClass.IsFilterOk = function (filter, environment)
{
    var _log = log4javascriptFactory.getLogger('widget manager');
    _log.setLevel(log4javascript.Level.WARN);

    try
    {
        if (filter == null)
            throw new Error("filter cannot be null");

        if (environment == null)
            throw new Error("environment cannot be null");

        if (filter instanceof WidgetsDatabase.WidgetFilterGroup)
            return filter.filters.length == 0 ? true : $.Enumerable.From(filter.filters).Any(function (t) { return WidgetsManagerClass.IsFilterOk(t, environment) });

        var target = environment.GetProperty(filter.appliesTo);

        if (filter.appliesTo == 'list-filters')
        {
            // check if required list filter is set
            var filterIsSet = $.Enumerable.From(target).Any(function (t) { return t['filter-field'] == filter.property });

            // if filter is not set - we consider it to pass
            if (!filterIsSet)
                return false;

            // at least one list filter should pass

            var result = $.Enumerable.From(target).Any(function (t)
            {
                if (t['filter-field'] != filter.property)
                    return false;

                var data = [];

                if (t['filter-value'] instanceof Array)
                    data = t['filter-value'];
                else
                    data.push(t['filter-value']);

                var shouldBeEqual = t['filter-compare'] == 'equal';
                var result = $.Enumerable.From(filter.value).Any(function (k) { return $.inArray(k, data) > -1 });

                return shouldBeEqual ? result : !result;
            });

            return result;
        }

        var property = target.GetProperty(filter.property);

        var evalLine = (property == 'true' || property == 'false' ? property : 'property ') + filter.condition + ' filter.value';
        var result = eval(evalLine);

        return result;
    }
    catch (error)
    {
        _log.error(error);
        throw error;
    }
}
;/// <summary>Widget View</summary>
function WidgetView()
{
    var createWidgetContainer = function (widgetId, x, y, html, width, xComparer)
    {
        return $('<div/>').append(               
                $('<li/>')
                        .attr('id', widgetId)
                        .addClass('box widget')
                        .addClass('width-' + width)
                        .addClass('row-' + y)
                        .addClass('col-' + x)
                        .addClass(xComparer < 3 ? "squeeze" : "")
                        .attr('data-list-row', y)
                        .attr('data-list-column', x)
                        .html(html)).html();
    }

    this.createTwoTwoWidget = function (widgetId, x, y, html)
    {
        return createWidgetContainer(widgetId, x, y, html, '50', 3)
        //return "<li id='w" + widgetId + "' class='width-50 box widget row-" + y + " col-" + x + " " + ((x < 3) ? "squeeze" : "") + "' data-list-row='" + y + "' data-list-column='" + x + "'></li>";
    }

    this.createOneOneWidget = function (widgetId, x, y, html)
    {
        return createWidgetContainer(widgetId, x, y, html, '25', 4)
        //return "<li id='w" + widgetId + "' class='width-25 box widget row-" + y + " col-" + x + " " + ((x < 4) ? "squeeze" : "") + "' data-list-row='" + y + "' data-list-column='" + x + "'></li>";
    }

    this.createTwoOneWidget = function (widgetId, x, y, html)
    {
        return createWidgetContainer(widgetId, x, y, html, '50', 3)
        //return "<li id='w" + widgetId + "' class='width-50 box widget row-" + y + " col-" + x + " " + ((x < 3) ? "squeeze" : "") + "' data-list-row='" + y + "' data-list-column='" + x + "'></li>";
    }
};$(document).ready(function () {
    //$("#collection-container .collection-pre-load").spin();
    preloadSpin($("#collection-container .collection-pre-load"));
    collectionController = new CollectionController();
});

/// <summary>Scrolls the window to an element based on the id of the element</summary>
/// <param name="element" type="String">The name identifier for the element that the window should scroll to</param>
function _scrollTo(element) {
    if (element == null)
        throw new Error('element cannot be null');
    var elementTop = $(element).offset().top;
    var windowHeight = window.innerHeight;
    if (windowHeight == null) {
        windowHeight = document.body.clientHeight;
    }
    var offsetY = window.pageYOffset;
    if (offsetY == null)
        offsetY = document.body.offsetTop;
    if (elementTop > (offsetY + windowHeight)) { 
        pageScroll(elementTop - (offsetY + windowHeight), 10);
    } else if (elementTop < offsetY) {
        pageScroll(elementTop - offsetY, -10);
    }
}
/// <summary>Recursive helper method to scroll the window by a specific scroll value. If the scroll value is greater than the scroll height the window will scroll.</summary>
/// <param name="scrollHeight" type="int">The total height to scroll the window</param>
/// <param name="scrollValue" type="int">The number of pixels to scroll the window</param>
/// <param name="callback" type="function">Callback function when finished scrolling</param>
function _pageScroll(scrollHeight, scrollValue, callback) {
    if ((scrollHeight - scrollValue) < 0) {
        window.scrollBy(0, scrollValue);
        scrolldelay = setTimeout('pageScroll(' + (scrollHeight - scrollValue) + ', ' + scrollValue + ', ' + callback + ')', 10);
    } else {
        if(typeof callback == 'function')
            callback.call();
    }
}
/// <summary>jQuery extension to lazy load a gallery</summary>
(function ($) {
    $.fn.lazyGallery = function (images, numToLoad) {
        return this.each(function () {
            var $this = $(this);
            var index = 0;
            var numLoaded = 0;
            var numLoad = 5;

            if (numToLoad != undefined)
                numLoad = numToLoad;

            $this.append('<div class="next"><a href="#">next</a></div><div class="previous"><a href="#">previous</a></div>');

            $this.find(".next a").bind('click', function () {
                changeImage(1);
                return false
            });
            $this.find(".previous a").bind('click', function () {
                changeImage(-1);
                return false;
            });

            for (var i = 0; i < images.length; i++) {
                var image = new Image();
                $(image).appendTo($this).wrap('<div class="image" />').load(function () {
                    if ($this.find(".image").index($(this).parent()) == 0)
                        $(this).parent().fadeIn();
                    numLoaded++;
                }).error(function () {
                    alert("fejl");
                }).attr('src', images[i]);
            }

            function changeImage(next) {
                var nextIndex;
                if (index + next < 0)
                    nextIndex = images.length - 1;
                else if (index + next >= images.length)
                    nextIndex = 0;
                else
                    nextIndex = index + next;

                $($this.find(".image").get(nextIndex)).fadeIn();
                $($this.find(".image").get(index)).fadeOut();
                index = nextIndex;
            }
        });
    };
})(jQuery);;var historyPluginAttached = false;
var ignoreHistoryEvent = false;
var _productId = null;
var menuFixInterval = null;

/// <summary>Collection controller</summary>
function CollectionController() {
    if (menuFixInterval != null) {
        clearInterval(menuFixInterval);
    }
    var _log = log4javascriptFactory.getLogger('collection controller');
    _log.setLevel(log4javascript.Level.WARN);

    _log.trace('initialized');


    var self = this;

    var effectTime = 500;

    var filtersData = $("#filter-init");
    var dataProductFeed = filtersData.attr("data-productfeed-url");

    this.shownProductsChangedEvent = new pandora.utils.Event(this);

    var activeClass = "active";
    var productController;
    var collection;
    var widgets;
    var categories;
    var filterDataSettings;
    var environment = new WidgetsEnvironmentClass();
    var view;
    var filteredCollection;
    var widgetParams = new Array();
    var collectionModel = new CollectionModel();
    var widgetsModel;
    self._sortBy = null;
    ignoreHistoryEvent = false;

    //Add a filter controller
    var filterController = new FilterController();
    //_log.debug('on creation filter control has following visible filters', filterController.getVisibleFilters());

    var currentProduct;

    var clickLock = (function () { //prevents product expansion/collapse during animations
        var locked = false;
        return {
            trylock: function () {
                if (locked) return false; /*console.log("lock");*/
                locked = true;
                return true;
            },
            unlock: function () { /*console.log("unlock");*/
                locked = false;
            },

            islocked: function () { return locked; }
        };
    })();

    $('#collection-container')
        .undelegate('a.p-link', 'click')
        .delegate('a.p-link', 'click', function (e) {
            ignoreHistoryEvent = true;
            var url = $(this).attr('href');
            url = /(#.+)/m.exec(url)[1];
            var productSmartCatalogueLink = window.SmartCatalogueLinks.parseHash(url);
            _productId = productSmartCatalogueLink.productId;
            _log.info('current product id', _productId);
        });

    var getProductFromUrl = function () {
        var pathName = document.location.pathname;
        var regex = /^\/.+?\/explore\/.+?\/.+?\/(.+)/i;

        var result = regex.exec(pathName);
        
        return result == null ? '' : result[1];
    }

    var product = getProductFromUrl();
    var smartCatalogueLink = window.SmartCatalogueLinks.parseHash(document.location.hash || '#' + product);

    smartCatalogueLink.productId = smartCatalogueLink.productId || product;

    if (smartCatalogueLink.productId != null) {
        smartCatalogueLink.productId = smartCatalogueLink.productId.toUpperCase();
    }

    var allDataLoaded = new $.Deferred();

    var onDataLoad = function (sender, args)
    {
        _log.trace('onDataLoad');
        if (!!args.collection)
        {
            collectionModel.onLoadEvent.detach(onDataLoad);
            collection = args.collection;
        }
        if (!!args.widgets)
        {
            widgets = args.widgets;
            widgetsModel.onLoadEvent.detach(onDataLoad);
        }
        if (!!args.categories)
        {
            categories = args.categories;
        }

        if (!!args.filterDataSettings)
        {
            filterDataSettings = args.filterDataSettings;
            allDataLoaded.resolve();
        }

        _log.debug('collection', collection ? 'resolved' : 'null', 'widgets', widgets ? 'resolved' : 'null', 'categories', categories ? 'resolved' : 'null');

        if (collection != undefined && widgets != undefined && categories != undefined)
        {
            _log.trace('onDataLoad begin');
            //main view...
            view = new CollectionView(filtersData, filterDataSettings);
            view.registerHoverFunctions($("#collection-container"));
            _log.trace('filtering collection');
            filteredCollection = filterController.getFilters().filterCollection(collection);

            // filter collection for promotion page if required
            var maxProducts = $('#promotion-init').attr('data-max-products');

            if (maxProducts && maxProducts != 'all')
            {
                maxProducts = parseInt(maxProducts);
                var categoryFilter = $('#filter-init [data-filter-field="cat"]').attr('data-filter-value');

                if (categoryFilter != undefined)
                {
                    categoryFilter = $.Enumerable.From(categoryFilter.split('|'));
                    var sortOrder = $('select.ddl-sortby').attr('data-filter-sort-mode');

                    // sort collection
                    filterController.getFilters().sortCollection(filteredCollection, sortOrder);

                    // now 
                    var data = $.Enumerable.From(filteredCollection)
                        .GroupBy('$["@cat"]', '$')
                        .Where(function (t) { return categoryFilter.Contains(t.Key()); });
                    console.log(data);
                    var result = [];
                    var itemsPerCategory = Math.ceil(maxProducts / categoryFilter.Count());

                    data
                        .ForEach(function (t)
                        {
                            if (result.length == maxProducts)
                            {
                                return;
                            }

                            var numberOfProductToTake = Math.min(itemsPerCategory, maxProducts - result.length);
                            result = result.concat(t.Take(numberOfProductToTake).ToArray());
                        }
                    );

                    result = $.Enumerable.From(result);

                    if (result.Count() < maxProducts) // we have less products than expected (because one or more categories doesn't contain required amount of items)
                    {
                        var flatData = data.SelectMany('$').Where(function (t)
                        {
                            return !result.Any(function (x)
                            {
                                return x['@id'] == t['@id'];
                            });
                        }).ToArray();

                        // take the rest of required items
                        var numberOfResults = result.Count();
                        result = result.Concat($.Enumerable.From(flatData).Take(maxProducts - numberOfResults));
                    }

                    collection = {};
                    result.ForEach(function (t) { collection[t["@id"]] = t; });
                    
                    filteredCollection = filterController.getFilters().filterCollection(collection);
                }
            }

            _log.trace('collection is filtered, contains ', filteredCollection.length, 'elements');

            if (smartCatalogueLink.sortBy != null)
            {
                var sortOptions = $('#sortby-dropdown-1 option').removeAttr('selected');
                var selectedOption = sortOptions.filter('[data-filter-sort-mode="' + smartCatalogueLink.sortBy + '"]').attr('selected', 'selected');

                var sortByText = selectedOption.text();

                if (sortByText != '')
                {
                    self._sortBy = smartCatalogueLink.sortBy;
                    $('#sortby-dropdown-1 span.text').text(sortByText).cufon();
                    filterController.getFilters().sortCollection(filteredCollection, smartCatalogueLink.sortBy);
                }
            } else
            {
                filterController.getFilters().sortCollection(filteredCollection, filterController.getDefaultSortOrder());
            }

            // if item number is specified then trying to put it into the first place in the list
            if (!!smartCatalogueLink.productId)
            {
                _productId = smartCatalogueLink.productId;
                // make sure collection contains specified item
                if (!$.Enumerable.From(filteredCollection).Any(function (t) { return t['@id'] == smartCatalogueLink.productId; }))
                {
                    var item = $.Enumerable.From(collection).FirstOrDefault(null, function (t) { return t.Key == smartCatalogueLink.productId; });

                    if (item != null)
                    {
                        filteredCollection.push(item.Value);
                    }
                }

                filteredCollection = $.Enumerable.From(filteredCollection).OrderBy(function (x)
                {
                    return x['@id'] == smartCatalogueLink.productId ? -1 : 0;
                }).ToArray();
            }

            self.loadCollectionProductsWithWidgets(); //(true, 3);

            // Showing main product if it is specified
            if (!!smartCatalogueLink.productId && filteredCollection && filteredCollection.length > 0 && filteredCollection[0]['@id'] == smartCatalogueLink.productId && clickLock.trylock())
            {
                currentProduct = filteredCollection[0];
                var currentProductView = $('#collection-container li[data-product-id=' + smartCatalogueLink.productId + ']');
                currentProductView.addClass(activeClass);
                productController = showPlaceholder(currentProduct, currentProductView, function () { clickLock.unlock(); });
            }

            _log.trace('onDataLoad end');
        }
    };

    collectionModel.onLoadEvent.attach(onDataLoad);
    collectionModel.getCollection(filtersData.attr('data-catalog'));

    widgetsModel = new WidgetsManagerClass(null, function(widgetsModel) {
        widgetsModel.onLoadEvent.attach(onDataLoad);
        widgetsModel.GetWidgets(environment);
    });

    /// <summary>Sets the sort method of the controller</summary>
    /// <param name="sortMode" type="String">String indicating the sort method to be applied. Defaults to newest</param>
    var setSortMethod = function (sender, args) {
        _log.trace('sorting updated');

        var sortMode = null;

        if (args)
            sortMode = args.sortOrder;

        self._sortBy = sortMode;
        setNewSmartCatalogueUrl();
    };

    /// <summary>Handles closing a product details view</summary>
    /// <param name="productId" type="String">The Id of the product details view to close</param>
    this.closeProductDetail = function (productId) {
        $(".collection li").remove("#" + productId);
        $(".collection li.active").removeClass("active");
    };

    // <summary>Loads a part of the collection injected with widgets if any are available</summary>
    /// <param name="data" type="jQuery/XML">The dataset to iterate over</param>
    /// <param name="clear" type="bool">A boolean indicating wether the collections placeholder should be cleared, e.g. when a new filter is applied by the user</param>
    /// <param name="numDisplayPage" type="int">THe number of items per page.</param>
    this.loadCollectionProductsWithWidgets = function (clear, numRows, numColumns, sortOrder, showAll) {
        if (filteredCollection == null)
            throw new Error('filteredCollection cannot be null');

        if (!!sortOrder) { // Resorting only in case if sortOrder is specified
            filterController.getFilters().sortCollection(filteredCollection, sortOrder);
        }

        //Setup...
        if (clear !== false)
            $("#collection-container").empty();
        if (numRows == null || numRows < 0)
            numRows = 10;
        if (numColumns == null || numColumns < 0)
            numColumns = 4;
        var numDisplayPage = numRows * numColumns;
        var totalLength = filteredCollection.length + this.calculateTotalWidgetsCount();
        _log.debug('loadCollectionProductsWithWidgets, collection contains', filteredCollection.length, 'elements');

        var newPageNumber = ($(".collection ul.page").length + 1);

            var newPageId = "page-" + newPageNumber;

            if (filteredCollection.length == 0) {
                $("#collection-container").append(view.createEmptyResult());
                $(".more-products:visible").fadeOut(200, function () { setFooter(); });
            } else if (totalLength > ((newPageNumber - 1) * numDisplayPage)) {
                var page = view.createProductPageShell(newPageId, newPageNumber);
                $("#collection-container").append(page);
                $("#" + newPageId).append(self.getPageItems(filteredCollection, newPageNumber, numColumns, numRows));
                loadImages("#" + newPageId);
                $(".more-products").fadeIn(200, function () { setFooter(); });
            }

        totalLength = filteredCollection.length + this.calculateTotalWidgetsCount();
        if (totalLength <= (newPageNumber * numDisplayPage)) {
            $(".more-products:visible").fadeOut(200, function () { setFooter(); });
        } else {
            $(".more-products").show();
            setFooter();
        }

        var lastProduct = $("#" + newPageId + " li.list-product:last-child");
        var lastRow = lastProduct.attr("data-list-row");
        var lastTop = parseInt(lastProduct.css("top"));
        $("#" + newPageId).height(lastTop + lastProduct.outerHeight(true));
        loadImages($("#" + newPageId + " .spot-image .image-wrapper"));
        setFooter();
        scrolled(false);
        $("div.bundle-spot .Optima").cufon();

        clickLock.unlock();
        currentProduct = undefined;

        $(this).trigger('new-page-added', [$("#" + newPageId), newPageNumber]);
    };

    this.calculateTotalWidgetsCount = function () {
        if (!widgets) {
            return 0;
        }

        var items = $.Enumerable.From($("#collection-container .widget")).Select(function (t) {
            return $(t)[0].id;
        }).ToArray();

        if (items.length == 0) {
            return widgets.length;
        }

        var count = $.Enumerable.From(items).Sum(function (t) {
            var item = $.Enumerable.From(widgets).Where(function (c) {
                return c.id == t;
            }).FirstOrDefault();

            if (item) {
                return item.width * item.height;
            }
            return 0;
        });
        return count;
    };
    //<summary>layout contains the number of empty rows before the first widget on each page ('first') and the number of empty rows
    //between each widget (spacing). 
    //spacing is given as a set (min,max) and a random number is chosen in the range, both inclusive.
    //the last page for which layout us specified will be reused for remaining pages.
    //modifies icon- and widget-objects, adding x, y and page numbers
    //returns the number of needed pages.</summary>
    function distribute(icons, widgets, pageWidth, pageHeight, layout) {
        if (!layout)
            layout = [{ first: { min: 0, max: 0 }, spacing: { min: 2, max: 5} }, //first page
				      {first: { min: 4, max: 6 }, spacing: { min: 100, max: 100}}]; //remaining pages. only one widget on each.

        var result = distributeMain(icons, widgets, pageWidth, pageHeight, layout);
        for (var i = 0; i < pageWidth - 1; ++i) //repeat pageWidth-1 times: in case the icons do not fill the last row, try to cram in extra unused widgets.
            distributeCram(icons, widgets, pageWidth, pageHeight, layout, result);

        return result.numPages;
    }

    //utility function used by distribute. if possible, fit in extra widgets to fill last row
    function distributeCram(icons, widgets, pageWidth, pageHeight, layout, intermediateResult) {
        var li = icons[icons.length - 1];
        var lastWidgetXEnd = -1;
        if (intermediateResult.usedWidgets > 0 && widgets[intermediateResult.usedWidgets - 1].page == icons[icons.length - 1].page) {
            var lw = widgets[intermediateResult.usedWidgets - 1];
            lastWidgetXEnd = lw.y + lw.h - 1 == li.y ? lw.x + lw.w : -1;
        }
        var lastRowPos = Math.max(li.x + 1, lastWidgetXEnd);

        if (lastRowPos != pageWidth) {
            //just try to place an unused widget in the empty space...
            for (var i = intermediateResult.usedWidgets; i < widgets.length; ++i) {
                if (widgets[i].h == 1 && widgets[i].w <= pageWidth - lastRowPos) {
                    widgets[i].y = icons[icons.length - 1].y;
                    widgets[i].x = lastRowPos;
                    widgets[i].page = intermediateResult.numPages - 1;

                    var tmp = widgets[i];
                    widgets[i] = widgets[intermediateResult.usedWidgets];
                    widgets[intermediateResult.usedWidgets] = tmp;
                    intermediateResult.usedWidgets++;
                    break;
                }
            }
        }
    }

    //utility function used by distribute. tages {min:x, max:y} and returns an integer in the range - both inclusive!
    function randn(range) {
        return Math.floor(Math.random() * (range.max - range.min + 1) + range.min);
    }

    //utility function used by distribute. returns { numPages: , usedWidgets: }
    function distributeMain(icons, widgets, pageWidth, pageHeight, layout) {
        for (var i = 0; i < widgets.length; ++i) {
            if (widgets[i].h > 2 || widgets[i].w > pageWidth)
                throw new "widget size not supported";
        }

        var page = 0;
        var wIdx = 0;
        var iIdx = 0;
        var widgetAlign = 0;

        while (true) {
            var pageRows = [];
            var row = randn(layout[Math.min(layout.length - 1, page)].first);
            var virtualIIdx = iIdx + row * pageWidth; //icons required to cover the widgets to be inserted
            while (wIdx < widgets.length && row + widgets[wIdx].h <= pageHeight) {
                var anticipated = (pageWidth - widgets[wIdx].w) * widgets[wIdx].h;
                if (virtualIIdx + anticipated > icons.length)
                    break;
                widgets[wIdx].y = row;
                widgets[wIdx].x = widgetAlign * (pageWidth - widgets[wIdx].w); //alternating 0 or (pageWidth - widgets[wIdx].w)
                widgets[wIdx].page = page;
                pageRows[row] = widgets[wIdx];
                if (widgets[wIdx].h == 2)
                    pageRows[row + 1] = widgets[wIdx];

                var rowincrement = widgets[wIdx].h + randn(layout[Math.min(layout.length - 1, page)].spacing);
                virtualIIdx += rowincrement * pageWidth - widgets[wIdx].w * widgets[wIdx].h;
                row += rowincrement;
                widgetAlign = widgetAlign ? 0 : 1;
                ++wIdx;
            }

            for (var row = 0; row < pageHeight; ++row) {
                for (var col = 0; col < pageWidth; ++col) {
                    if (pageRows[row] && pageRows[row].x <= col && pageRows[row].x + pageRows[row].w - 1 >= col)
                        continue;
                    icons[iIdx].x = col;
                    icons[iIdx].y = row;
                    icons[iIdx].page = page;
                    if (++iIdx >= icons.length) { //all done.
                        for (var k = wIdx; k < widgets.length; ++k) {
                            widgets[k].page = -1; //unused
                        }
                        return { numPages: page + 1, usedWidgets: wIdx };
                    }
                }
            }
            ++page;
        }
    }


    /// <summary>Returns all items for a given page as a string containing the HTML that can be inserted in the page</summary>
    this.getPageItems = function (items, pageNumber, pageWidth, pageHeight) {
        pageNumber = pageNumber - 1;

        //layout contains the number of empty rows before the first ('first') and between each ('spacing') widget. min and max are inclusive.
        var layout = [{ first: { min: 2, max: 3 }, spacing: { min: 1, max: 2} }, //first page
				      {first: { min: 3, max: 7 }, spacing: { min: 100, max: 100}}]; //remaining pages. only one widget on each.
        var widgetView = new WidgetView();

        var ewidgets = [];
        for (var i = 0; i < widgets.length; ++i) {
            ewidgets.push({ data: widgets[i], w: widgets[i].width, h: widgets[i].height });
        }
        var eitems = [];
        for (var i = 0; i < items.length; ++i) {
            eitems.push({ data: items[i] });
        }
        distribute(eitems, ewidgets, pageWidth, pageHeight, layout);

        var str = new Array();

        for (var i = 0; i < ewidgets.length; ++i) {
            if (ewidgets[i].page == pageNumber) {
                var width = ewidgets[i].w;
                var height = ewidgets[i].h;
                var id = ewidgets[i].data.id;
                var x = ewidgets[i].x + 1;
                var y = ewidgets[i].y + 1;

                if (height == 2 && width == 2)
                    str.push(widgetView.createTwoTwoWidget(id, x, y, ewidgets[i].data.html));
                else if (height == 1 && width == 2)
                    str.push(widgetView.createTwoOneWidget(id, x, y, ewidgets[i].data.html));
                if (height == 1 && width == 1)
                    str.push(widgetView.createOneOneWidget(id, x, y, ewidgets[i].data.html));
            }
        }

        var visibleIds = [];
        var visibleFilters = filterController.getVisibleFilters();

        for (var i = 0; i < eitems.length; ++i) {
            if (eitems[i].page == pageNumber) {
                str.push(view.createPageProduct(eitems[i].data, eitems[i].x + 1, eitems[i].y + 1, visibleFilters, self._sortBy));
                visibleIds.push(eitems[i].data["@id"]);
            }
        }

        self.shownProductsChangedEvent.notify({ items: visibleIds });
        return str.join('');
    };

    function showPlaceholder(productModel, productView, callback) {
        var parentView = productView.parent();
        var offset = productView.offset().top - parentView.offset().top + productView.outerHeight(true);

        var newRow = parseInt(productView.attr("data-list-row"), 10);
        var animateListProducts = parentView.find("li").filter(function () { return parseInt($(this).attr("data-list-row"), 10) > newRow; });

        var placeHolder = $(view.createProductPlaceholder()).css({ top: offset, height: 0 }).appendTo(parentView);
        var pc = self.createProduct(productModel, placeHolder, animateListProducts, null, callback);
        slideProductList(placeHolder, 0, 400, parentView, animateListProducts, function () {
            pc.onPlaceholderInitDone();
        });
        return pc;
    }

    this.createProduct = function (product, container, animateListProducts, forceHeight, callback, setSingleTopViewID) {
        var page = container.parent();
        var pc = new ProductController({
            product: product,
            productID: product["@id"],
            setSingleTopViewID: setSingleTopViewID,
            container: container,
            effectTime: effectTime,
            collection: collection,
            onClose: function (cb) {
                //if (animateListProducts && animateListProducts.length > 0) {
                slideProductList(container, container.height(), 0, page, animateListProducts, cb);
                //}
            },
            productClosedItself: function () {
                $('#collection-container li[data-product-id=' + currentProduct['@id'] + ']').removeClass(activeClass);
                currentProduct = undefined;
            },
            adjustContainerHeight: function (height, onSlidingDone) {
                if (container.hasClass('placeholder')) {
                    slideProductList(container, container.height(), height, page, animateListProducts, function () {
                        onSlidingDone();
                    });
                } else {
                    container.animate({ height: ((height > container.height() ? "+=" : "-=") + Math.abs(height - container.height())) }, effectTime, function () {
                        onSlidingDone();
                    });
                }
            },
            onOpenAndFadeinCompleted: function () {
                callback && callback();
            },
            clickLock: clickLock,
            categories: categories,
            filtersData: filtersData,
            filterDataSettings: filterDataSettings
        });
        return pc;
    };

    var slideProductList = function (container, oldHeight, wantedHeight, productPage, animateListProducts, callback) {
        container.height(oldHeight);
        var cssChange = wantedHeight - oldHeight;

        //Create base positions
        var positions = [];
        animateListProducts.each(function (idx) {
            positions.push(parseInt($(this).css('top')));
        });

        var scrollDist = container.offset().top - ($(window).scrollTop() + $("#menu-container #menu").height() + $("#filter-container .filter").height() + 42);

        var oldNow = productPage.height();
        productPage.animate({ height: ((wantedHeight > oldHeight ? "+=" : "-=") + Math.abs(cssChange)) }, {
            step: function (now, fx) {
                //change offset on listproducts
                var change = now - oldNow;
                animateListProducts.each(function (idx) {
                    positions[idx] += change;
                    $(this).css('top', positions[idx]);
                });
                oldHeight += change;
                container.height(oldHeight);
                oldNow = now;
                if (wantedHeight > 0) { //window scroll
                    var animDist = fx.end - fx.start;
                    var scrolinc = (change / animDist) * scrollDist;
                    window.scrollBy(0, scrolinc);
                }
            },
            duration: effectTime,
            complete: function () {
                if (wantedHeight == 0)
                    container.remove();
                if (callback)
                    callback();
            }
        });
    };

    var setNewSmartCatalogueUrl = function() {
        var existingCatalogueLink = window.SmartCatalogueLinks.parseHash(document.location.hash || '#');

//        if (existingCatalogueLink.productId != null) {
            _productId = null;
//        }

        var newSmartCatalogueLink = window.SmartCatalogueLinks.buildHash(filterController.getVisibleFilters(), _productId, self._sortBy);
        _log.debug('visible filters', filterController.getVisibleFilters(), 'new smart catalogue url', newSmartCatalogueLink);

        $.History.go(newSmartCatalogueLink);
    };

    /// <summary>Handler for when the filters are updated</summary>
    var filterUpdated = function (sender, args) {
        _log.trace('filter updated');

        if (args.hasOwnProperty('sortOrder')) {
            self._sortBy = args.sortOrder;
        }

        setNewSmartCatalogueUrl();
    };
    //Attach onSortOrderChanged event
    filterController.onSortOrderChanged.attach(setSortMethod);
    //Attach onFilterChanged event
    filterController.onFilterChanged.attach(filterUpdated);

    //var lastScrollPos;
    $(window).unbind('scroll').scroll(function () {
        scrolled(true);
    });
    
    menuFixInterval = setInterval(function() {
        scrolled(true);
    }, 300);

    var scrolled = function (mode) {

        var menu = $("#menu-container #menu");

        var menuItems = $('.list-before .list-menu-item', menu);
        var zIndex = menuItems.css('z-index');
        menuItems.css('z-index', zIndex == 'auto' ? '100' : 'auto');

        var filterContainer = $("#filter-container");
        var menuPos = $("#menu-container").offset().top;
        var menuHeight = menu.removeAttr('style').height();
        var scrollTop = $(window).scrollTop();
        
        var collectionTop = $('#collection-container').offset().top;
        var filterHeight = filterContainer.height();
        if (!self.fullMenuHeight) { //first time...
            self.fullMenuHeight = menuHeight;
        }

        //Add fixed to menu
        if (scrollTop > menuPos) {
            menu.addClass("fixed");
        } else if (scrollTop <= menuPos) {
            menu.removeClass("fixed");
        }

        if (scrollTop + self.fullMenuHeight > collectionTop - filterHeight) {
            menuHeight = Math.max(44, collectionTop - filterHeight - scrollTop);
            menu.height(menuHeight);
            menu.find(".container .menu").height(menuHeight);
            menu.find(".container .menu h1").css({ 'top': ((menuHeight / 2) - 19) });

            $("#filter-container .filter").addClass("fixed");
            $("#filter-container .filter").css({ 'top': menuHeight });
            //filterContainer.css("top", scrollTop);
        }
        else if (scrollTop + self.fullMenuHeight <= collectionTop - filterHeight) {
            menu.height(self.fullMenuHeight);
            menu.find(".container .menu").height(self.fullMenuHeight);
            menu.find(".container .menu h1").css({ 'top': ((self.fullMenuHeight / 2) - 19) });
            $("#filter-container .filter").removeClass("fixed").css({ 'top': 0 });
        }

        var footer = $("#footer");
        if (footer.hasClass("footerAbsolute")) {
            menu.removeClass("fixed");
            menu.height(self.fullMenuHeight);
            $("#filter-container .filter").removeClass("fixed").css({ 'top': 0 });
        }
    };

    /// <summary>Initiates handler for a click on the button to show more products</summary>
    ///Load next page with products
    $(".more-products a").unbind('click').click(function() {
        _log.trace('more products button clicked');

        var showAll = $(this).hasClass("all-products-button");

        $(".more-products a").addClass("hide");
        $(".more-products .spinner").removeClass("hide");
        
        setTimeout(function() {
            if (clickLock.trylock()) {
                // Same product clicked and should be closed
                if (currentProduct) {
                    $('#collection-container li[data-product-id=' + currentProduct['@id'] + ']').removeClass(activeClass);
                    productController.closeProduct(function() {
                        clickLock.unlock();
                        self.loadCollectionProductsWithWidgets(false, null, null, null, showAll);
                    });
                } else {
                    self.loadCollectionProductsWithWidgets(false, null, null, null, showAll);
                }
                currentProduct = undefined;
            }
            $(".more-products .spinner").addClass("hide");
            $(".more-products a").removeClass("hide");
            return false;
        }, 200);
        
        return false;
    });

    $.when(allDataLoaded.promise()).then(function () {
        _log.trace('allDataLoaded');
        if (historyPluginAttached) {
            return;
        }

        historyPluginAttached = true;

        $.History.bind(function (hash) {
            if (hash == null || hash.length == 0) {
                hash = '#';
            }

            if (hash[0] != '#') {
                hash = '#' + hash;
            }

            _log.debug('hash updated', hash);
            
            var productID = window.SmartCatalogueLinks.parseHash(hash).productId;

            if (productID != '') {
                $('li[data-product-id="' + productID + '"]').click();
            } else {
                $('.placeholder .product-top-container .close a').click();
            }

            if (ignoreHistoryEvent) {
                ignoreHistoryEvent = false;
                _log.debug('history event is ignored');
                return;
            }

            filterController.onFilterChanged.clear();
            filterController.onSortOrderChanged.clear();

            collectionController = new CollectionController();
        });
    });

    $('#collection-container').undelegate('li.list-product', 'click')
        .delegate('li.list-product', 'click', function () {
        if (!clickLock.trylock()) {
            return;
        }

        var newProductView = $(this);
        var newProduct = collection[newProductView.attr("data-product-id")];

        if (currentProduct && currentProduct['@id'] == newProduct['@id']) { // Same product clicked, removing details
            productController.closeProduct(function () { clickLock.unlock(); });
            $('#collection-container li[data-product-id=' + currentProduct['@id'] + ']').removeClass(activeClass);
            currentProduct = undefined;
        }
        else {
            newProductView.addClass(activeClass);
            if (!currentProduct) { // New product clicked
                productController = showPlaceholder(newProduct, newProductView, function () { clickLock.unlock(); });
            }
            else {
                var newRow = parseInt(newProductView.attr("data-list-row"), 10);

                var currentProductView = $('#collection-container li[data-product-id=' + currentProduct['@id'] + ']');
                currentProductView.removeClass(activeClass);

                var currentProductRow = parseInt(currentProductView.attr("data-list-row"), 10);
                if (newRow == currentProductRow) {
                    var animateListProducts = newProductView.parent().find("li").filter(function () {
                        return parseInt($(this).attr("data-list-row"), 10) > newRow;
                    });
                    // Same row as open product so close open product and open new product in placeholder
                    productController.onClose = function () {
                        productController = self.createProduct(newProduct, newProductView.parent().find("li.placeholder"), animateListProducts, newProductView.parent().find("li.placeholder").outerHeight(true), function () {
                            clickLock.unlock();
                        });
                        productController.onPlaceholderInitDone();
                    };
                    productController.closeProduct();
                }
                else {
                    // Does not take the height of filters into account
                    var viewPortTop = $(window).scrollTop();
                    var viewPortBottom = viewPortTop + $(window).height();
                    var openProductTop = $('.placeholder').offset().top;
                    var openProductHeight = $('.placeholder').height();
                    var openProductBottom = openProductTop + openProductHeight;

                    if (viewPortBottom < openProductTop || viewPortTop > openProductBottom) {
                        // Emidiately collapse open product if it is not in the viewport 
                        if (viewPortTop > openProductBottom)
                            $(window).scrollTo(viewPortTop - openProductHeight);
                        $('.placeholder').remove();

                        var currentProductParentView = currentProductView.parent();

                        var containerHeight = currentProductParentView.height() - openProductHeight;
                        currentProductParentView.height(containerHeight);

                        currentProductParentView.find("li").filter(function () {
                            return parseInt($(this).attr("data-list-row"), 10) > currentProductRow;
                        }).each(function () {
                            var myTop = parseInt($(this).css('top'), 10);
                            $(this).css('top', (myTop - openProductHeight));
                        });
                        productController = showPlaceholder(newProduct, newProductView, function () { clickLock.unlock(); });
                    }
                    else {
                        // New row so close existing row and open new row
                        productController.closeProduct(function () {
                            productController = showPlaceholder(newProduct, newProductView, function () { clickLock.unlock(); });
                        });
                    }
                }
            }
            currentProduct = newProduct;
        }
    });

    window.generateSmartCatalogueUrlForProductID = function(productID) {
        var visibleFilters = filterController.getVisibleFilters();
        return window.SmartCatalogueLinks.buildHash(visibleFilters, productID, self._sortBy);
    };
}

;var ProductDetailsViewType = {
                                GENERAL: 0,
                                WATCH: 1,
                                NECKLACE: 2
                            };

/// <summary>Product View</summary>
function ProductView(filtersData, filterDataSettings, categories, collection) {
    var self = this;

    var ressourceUrl = filterDataSettings["imageurl"];
    if (ressourceUrl.lastIndexOf('/') != ressourceUrl.length - 1)
        ressourceUrl += "/";

    var ifProductAttrContains = function(joinedValues, value)
    {
        var values = joinedValues.split("|");
        return $.inArray(value, values) != -1;
    };

    var getProductAttr = function(product, attrKey, groupName, args)
    {
        var excludeAttr = args ? args.excludeAttr : null;
        if (!excludeAttr || !excludeAttr.length) excludeAttr = [excludeAttr];
        var attrs = product["@" + attrKey];
        if (!attrs)
            return [];
        attrs = attrs.split('|');
        var result = [];

        for (var i = 0; i < attrs.length; ++i)
        {
            var continue_attr = false;
            for (var j = 0; j < excludeAttr.length; ++j)
            {
                if (attrs[i] == excludeAttr[j])
                {
                    continue_attr = true;
                }
            }
            if (continue_attr)
                continue;
            result.push(categories[groupName][attrs[i]]);
        }
        return result;
    };

    /// <summary>Creates the detailed view for a product</summary>
    this.createProductDetail = function (product, productVariants) {
        var storefinder_url = $("#static-data-init").attr("data-storefinder-url");
        var sizeguide_url = $("#static-data-init").attr("data-sizeguide-url");
        var show_like = $("#static-data-init").attr("data-show-like") == "true";
        var facebook_width = $("#static-data-init").attr("data-facebook-width");
        var facebook_locale = $("#static-data-init").attr("data-facebook-locale");

        var producturl = product["@url"];

        // views
        if (ifProductAttrContains(product["@cat"], "11"))
            self.view = ProductDetailsViewType.WATCH;
        else if (ifProductAttrContains(product["@subcol"], "06") && (ifProductAttrContains(product["@subcat"], "06") || ifProductAttrContains(product["@subcat"], "27") || (ifProductAttrContains(product["@subcat"], "13") && (window.LastView == ProductDetailsViewType.NECKLACE || window.location.pathname.match(/\/explore\/collections\/necklace$/)))))
            self.view = ProductDetailsViewType.NECKLACE;
        else self.view = ProductDetailsViewType.GENERAL;

        window.LastView = self.view;
        var variantCount;
        var productSameSub;

        if (self.view == ProductDetailsViewType.GENERAL) {
            variantCount = productVariants ? productVariants.length : 0;
        } else {
            productSameSub = self.createSubCategoryView(product);
            variantCount = productSameSub.count;
        }

        var imageUrl = ressourceUrl + product["@col"] + '/400x400/' + product["@id"] + '.png';
        var productDetail = ['<div id="id' + product["@id"] + '" class="product '
            + (self.view == ProductDetailsViewType.WATCH ? " watch-view" : "") +
                (variantCount > 0 ? " more-than-zero-variants" : "") + '"><div class="product-top-container"><div class="close"><a href="#">X</a></div>'];

        if (self.view == ProductDetailsViewType.WATCH && product["@subcat"] == "101") {
            productDetail.push('<div class="about-btn-container">' + self.createButton(DictionaryItems.A.About) + '</div>');
            productDetail.push('<div class="about-overlay-container" style="display: none;">' + self.createAboutOverlay(product) + '</div>');
        }

        productDetail.push('<div class="images box"><div class="image"><img src="' + imageUrl + '" alt="" /></div>');

        if (product["@design"] == 'true' && self.view != ProductDetailsViewType.WATCH) {
            productDetail.push('<div class="create-combine"><a href="#create-combine"><span>' + DictionaryItems.C.CreateAndCombine + '</span><span class="create-and-combine">' + DictionaryItems.W.WithThisItem + '</span></a></div>');
        }
        else if (product["@design"] == 'true' && self.view == ProductDetailsViewType.WATCH) {
            productDetail.push('<div class="create-combine"><a href="#create-combine"><span>' + DictionaryItems.WatchRelated.DesignYourOwnWatch + '</span><span class="create-and-combine">' + DictionaryItems.W.WithThisItem + '</span></a></div>');
        }


        productDetail.push('</div>');

        productDetail.push('<div class="related-item-wrapper"><div class="related-items">');

        if (self.view == ProductDetailsViewType.WATCH || self.view == ProductDetailsViewType.NECKLACE) {
            productDetail.push(productSameSub.content);
        } else if (productVariants && productVariants.length > 0) {
            productDetail.push('<div class="scroll-wrapper product-variants"><div class="scroll-panel-wrapper num-products-' + ((productVariants.length <= 5) ? productVariants.length : '5') + '"><div class="scroll-panel" data-page-size="5"><ul>');
            for (var i = 0; i < productVariants.length; i++) {
                var variantUrl = ressourceUrl + productVariants[i]["@col"] + '/152x79/' + productVariants[i]["@id"] + '.png';
                productDetail.push('<li class="width-20 item"><a href="' + window.generateSmartCatalogueUrlForProductID(productVariants[i]["@id"]) + '" data-itemnumber="' + productVariants[i]['@id'] + '" ><img src="' + variantUrl + '" alt="' + productVariants[i]['@name'] + '" /></a></li>');
            }
            productDetail.push('</ul></div></div>');
            productDetail.push('</div>');
        }
        productDetail.push('</div></div>');
        productDetail.push('<div class="info box">');
        productDetail.push('<h2>' + product["@name"] + '</h2>');

        productDetail.push('<div class="material-info">');
        productDetail.push('<dl class="info">');
        if (product["@material"] != "01") {
            productDetail.push('<dt>' + DictionaryItems.M.Material + '</dt><dd>' + getProductAttr(product, "material", "Material") + '</dd>');
        }
        productDetail.push('<dt>' + DictionaryItems.M.Metal + '</dt><dd>' + getProductAttr(product, "metal", "Metal") + '</dd>');

        if (product["@stone"] != "01") {
            var stone = getProductAttr(product, "stone", "Stone").join(", ");
            if (stone != "") {
                productDetail.push('<dt>' + DictionaryItems.S.Stones + '</dt><dd>' + getProductAttr(product, "stone", "Stone").join(", ") + '</dd>');
            }
        }

        if (product["@color"] != "01") {
            productDetail.push('<dt>' + DictionaryItems.C.Color + '</dt><dd>' + getProductAttr(product, "color", "Color").join(", ") + '</dd>');
        }

        productDetail.push('<dt>' + DictionaryItems.N.Number + '</dt><dd>' + product["@id"] + '</dd></dl>');
        //productDetail.push('<p>' + product.description["#cdata-section"] + '</p>');
        productDetail.push('</div>');

        //IMPORTANT: Product description class names are involved in JQuery selectors.

        if (product.desc) {
            var descriptionText = product.desc["#cdata-section"];
            if (!Utils.isNullOrEmpty(descriptionText)) {

                productDetail.push('<div class="description">');
                productDetail.push('<div class="wrapper">');
                productDetail.push('<div class="description-top"></div>');

                productDetail.push('<div class="description-content">');

                if (descriptionText.length > 50) {
                    //49 - because JS is 0 based.
                    productDetail.push('<p class="summary">' + Utils.truncateTextWithoutCuttingWords(descriptionText, 49) + '.. <a class="read-more">' + DictionaryItems.R.ReadMore + '</a></p>');
                    productDetail.push('<p class="full-description" style="display:none;">' + descriptionText + '</p>');
                } else {
                    productDetail.push('<p>' + descriptionText + '</p>');
                }
                productDetail.push('</div>');

                productDetail.push('<div class="description-footer"></div>');
                productDetail.push('</div>');
                productDetail.push('</div>');
            }
        }
        /**/

        var fmt = Utils.NumberFormatter.getFormatter(filterDataSettings["thousandsseparator"], filterDataSettings["decimalseparator"]);

        if (product["@state"] != 'discontinued' && product["@price"] != '0') {
            productDetail.push('<div class="price">' + fmt(product["@price"]) + " " + filterDataSettings["currency"] + '</div>');
        } else if (product["@state"] == 'discontinued') {
            productDetail.push('<div class="discontinued">' + DictionaryItems.D.DiscontinuedItemDescription + '</div>');
        }
        else {
            productDetail.push('<div class="price">&nbsp;</div>');
        }

        //        if (product["@state"] == 'discontinued')
        //        {
        //            productDetail.push('<div class="discontinued">' + DictionaryItems.D.DiscontinuedItemDescription + '</div>');
        //        }

        if (filtersData.attr("data-ecom") == 'true' && product["@ecom"] == 'true') {
            productDetail.push('<div class="add-to-bag">');
            productDetail.push('<a href="#" class="button add-to-bag-button"><span class="l">&nbsp;</span><span class="c"><span class="input" onclick="return false;" ><input type="text" value="1" id="amount-to-add" onclick="return false;" /></span><span class="text">' + DictionaryItems.Ecom.AddToBag + '<span class="bullet">&nbsp;</span></span></span><span class="r">&nbsp;</span></a>');
            productDetail.push('</div>');
            productDetail.push('<div class="store-finder"><a href="' + storefinder_url + '">' + DictionaryItems.F.FindNearestStore + '</a></div>');
        } else {
            productDetail.push('<div class="add-to-bag">');
            productDetail.push('<a href="' + storefinder_url + '" class="button find-stores-button"><span class="l">&nbsp;</span><span class="c"><span class="bullet">&nbsp;</span>' + DictionaryItems.F.FindNearestStore + '</span><span class="r">&nbsp;</span></a>');
            productDetail.push('</div>');
        }

        if (sizeguide_url != null && sizeguide_url != '') {
            productDetail.push('<div class="size-guide"><a href="' + sizeguide_url + '" target="_blank">' + DictionaryItems.S.SizeGuide + '</a></div>');
        }

        productDetail.push('<div class="box pandora-club">');
        productDetail.push('<div class="pandora-club-divider">&nbsp;</div>');
        productDetail.push('<div class="add-to-wish-list"><a href="#add-wish-list" class="wish-list-img"><span class="bullet">&nbsp;</span><span>' + DictionaryItems.A.AddToWishes + '</span></a></div>');
        productDetail.push('<div class="remove-from-wish-list"><a href="#remove-wish-list" class="wish-list-img"><span class="bullet">&nbsp;</span><span>' + DictionaryItems.R.Remove + '</span></a></div>');
        productDetail.push('<div class="add-to-my-jewellery"><a href="#add-my-jewellery" class="add-to-jewellery-img"><span class="bullet">&nbsp;</span><span>' + DictionaryItems.J.JewelleryAddToMine + '</span></a></div>');
        productDetail.push('<div class="remove-from-my-jewellery"><a href="#remove-my-jewellery" class="add-to-jewellery-img"><span class="bullet">&nbsp;</span><span>' + DictionaryItems.R.Remove + '</span></a></div>');
        productDetail.push('</div>');
        productDetail.push('</div>');

        productDetail.push('<div class="box share">');
        if (show_like) {
            productDetail.push('<div class="share-facebook">');
            productDetail.push('<iframe src="http://www.facebook.com/plugins/like.php?action=like&amp;api_key=187699144604345&amp;width=' + facebook_width + '&amp;stream=false&amp;header=false&amp;height=21&amp;href=' + encodeURI(producturl) + '&amp;layout=button_count&amp;locale=' + facebook_locale + '&amp;node_type=link&amp;sdk=joey&amp;show_faces=false" scrolling="no" frameborder="0" style="border: none; overflow: hidden; width: ' + facebook_width + '" allowtransparency="true" height="21" width="' + facebook_width + '"></iframe>');
            productDetail.push('</div>');
        }
        productDetail.push('<div id="share-other-' + product["@id"] + '" class="share-other"><a class="addthis_button_facebook">&nbsp;</a><a class="addthis_button_twitter">&nbsp;</a><a class="addthis_button_email">&nbsp;</a><a class="addthis_button_google_plusone" g:plusone:count="false" g:plusone:size="small">&nbsp;</a></div>');
        productDetail.push('</div>');

        productDetail.push('</div>');

        if (self.view == ProductDetailsViewType.WATCH || self.view == ProductDetailsViewType.NECKLACE) {
            var productInfo = [];

            if (self.view == ProductDetailsViewType.WATCH) {
                var subcollections = product["@subcol"].split('|');
                for (var j = 0; j < subcollections.length; j++) {
                    if (!ifProductAttrContains(product["@col"], subcollections[j])) {
                        productInfo = $("#subcollections").find("li[data-key='" + subcollections[j] + "']");
                        break;
                    }
                }
            } else {
                productInfo = $("#necklace-data-init");
            }

            if (productInfo.length) {
                productDetail.push('<div class="product-bottom-container">');
                productDetail.push('<div class="product-image image"><img src="' + productInfo.attr("data-image-url") + '" /></div>');
                productDetail.push('<div class="product-description"><h2>' + productInfo.attr("data-title") + '</h2><p>' + productInfo.attr("data-description") + '</p></div>');
                productDetail.push('<div class="clear"></div></div>');
            }

        }
        productDetail.push('<div class="clear"></div>');

        // Add ZoomMagic window
        //productDetail.push('<div class="zoomWindow"></div>');

        productDetail.push('</div>');
        return productDetail.join("");
    };

    this.createAboutOverlay = function (product) {
        var imageUrl = ressourceUrl + product["@col"] + '/236x190/' + product["@id"] + '.png';
        var specificationsUrl = ressourceUrl.replace("jewellery", "specifications") + product["@col"] + '/' + product["@id"] + '.png';
        var fmt = Utils.NumberFormatter.getFormatter(filterDataSettings["thousandsseparator"], filterDataSettings["decimalseparator"]);

        var txt = [];
        var a = function (s) { txt.push(s); };

        a('<div class="overlay-top">');
        a('<h2>' + product["@name"] + '</h2>');
        a('</div>');
        a('<div class="overlay-content">');
        a('<div class="divider"></div>');
        a('<div class="overlay-content-left">');
        a('<img src="' + imageUrl + '" />');
        a('<p>');
        if (product.desc) {
            a(product.desc["#cdata-section"]);
        }
        a('</p>');
        if (product["@interchangeable"] != "2") {
            a('<h4 class="watch">' + DictionaryItems.I.Interchangeable + ':</h4>');
            a('<div class="watch-interchangeable">');
            if (product["@interchangeable"].indexOf("4") >= 0) {
                a('<div><span class="valid">&nbsp;</span><p>' + DictionaryItems.WatchRelated.Strap + '</p></div>');
            }
            if (product["@interchangeable"].indexOf("8") >= 0) {
                a('<div><span class="valid">&nbsp;</span><p>' + DictionaryItems.WatchRelated.Bezel + '</p></div>');
            }
            a('</div>');
        }

        var generalInfo = $("#watch-data-init");
        a('<div class="watch-product-info-container"><h4>' + generalInfo.attr("data-swiss-title") + '</h4><p>' + generalInfo.attr("data-swiss-description") + '</p></div>');
        a('<div class="watch-product-info-container"><h4>' + generalInfo.attr("data-sapphire-title") + '</h4><p>' + generalInfo.attr("data-sapphire-description") + '</p></div>');
        a('<div class="watch-product-info-container"><h4>' + generalInfo.attr("data-diamond-title") + '</h4><p>' + generalInfo.attr("data-diamond-description") + '</p></div>');
        a('</div>');
        a('<div class="overlay-content-right">');

        a('<h4 class="watch">' + DictionaryItems.WatchRelated.Features + ':</h4>');
        a('<dl><dt>' + DictionaryItems.WatchRelated.Watch + ':</dt><dd>' + getProductAttr(product, "timedisplay", "WatchTimeDisplay", { excludeAttr: ["16", "32"] }).join(", ") + '</dd></dl>');
        var date = getProductAttr(product, "timedisplay", "WatchTimeDisplay", { excludeAttr: ["2", "4", "8"] }).join(", ");
        if (date != "") {
            a('<dl><dt>' + DictionaryItems.WatchRelated.Date + ':</dt><dd>' + getProductAttr(product, "timedisplay", "WatchTimeDisplay", { excludeAttr: ["2", "4", "8"] }).join(", ") + '</dd></dl>');
        }
        if (product["@quantifier"] != "2") {
            a('<dl><dt>' + DictionaryItems.WatchRelated.QuantifierEnum.Chronograph + ':</dt><dd>' + getProductAttr(product, "quantifier", "WatchQuantifier").join(", ") + '</dd></dl>');
        }
        a('<dl><dt>' + DictionaryItems.WatchRelated.WaterResistance + ':</dt><dd>' + getProductAttr(product, "waterresistance", "WatchWaterResistance").join(", ") + '</dd></dl>');
        a('<dl><dt>' + DictionaryItems.WatchRelated.Diamonds + ':</dt><dd>' + getProductAttr(product, "diamond", "WatchDiamond").join(", ") + '</dd></dl>');
        a('<dl><dt>' + DictionaryItems.WatchRelated.Case + ':</dt><dd>' + getProductAttr(product, "case", "WatchMaterialType").join(", ") + '</dd></dl>');
        a('<dl><dt>' + DictionaryItems.WatchRelated.Dial + ':</dt><dd>' + getProductAttr(product, "dial", "WatchDial").join(", ") + '</dd></dl>');
        if (product["@strapbracelet"] != "2") {
            a('<dl class="last"><dt>' + DictionaryItems.WatchRelated.StrapBracelet + ':</dt><dd>' + getProductAttr(product, "strapbracelet", "WatchMaterialType").join(", ") + '</dd></dl>');
        }
        a('<h4>' + DictionaryItems.WatchRelated.Dimensions + ':</h4>');

        a('<div class="spec-img" imagesrc="' + specificationsUrl + '" ></div>');

        if (product["@width"] != "0") {
            a('<dl><dt>' + DictionaryItems.WatchRelated.Width + ':</dt><dd>' + fmt(product["@width"]) + ' / ' + fmt(parseInt(product["@width"]) / 25.4) + '</dd></dl>');
        }
        if (product["@height"] != "0") {
            a('<dl><dt>' + DictionaryItems.WatchRelated.Height + ':</dt><dd>' + fmt(product["@height"]) + ' / ' + fmt(parseInt(product["@height"]) / 25.4) + '</dd></dl>');
        }
        if (product["@diameter"] != "0") {
            a('<dl><dt>' + DictionaryItems.WatchRelated.Diameter + ':</dt><dd>' + fmt(product["@diameter"]) + ' / ' + fmt(parseInt(product["@diameter"]) / 25.4) + '</dd></dl>');
        }
        if (product["@casethickness"] != "0") {
            a('<dl><dt>' + DictionaryItems.WatchRelated.CaseThickness + ':</dt><dd>' + fmt(product["@casethickness"]) + ' / ' + fmt(parseInt(product["@casethickness"]) / 25.4) + '</dd></dl>');
        }
        if (product["@weigth"] != "0") {
            a('<dl class="last"><dt>' + DictionaryItems.WatchRelated.Weight + ':</dt><dd>' + fmt(product["@weigth"]) + '</dd></dl>');
        }
        a('</div>');
        a('</div>');

        return txt.join('');
    };



    this.createButton = function(text)
    {
        return '<a href="" class="button white"><span class="l">&nbsp;</span><span class="c">' + text + '</span><span class="r">&nbsp;</span></a>';
    };

    var createGeneralSubCategoryView = function (product, productCategories, productCategoryNames, subcat, specificSubcollection, excludeCollection) {
        var nonEmptyCategories = {};

        if (!subcat)
            subcat = product["@subcat"];

        var productDetail = [];
        var mysubcols;

        if (specificSubcollection) {
            mysubcols = specificSubcollection.split('|'); ;
        }
        else {
            mysubcols = product["@subcol"].split('|');
        }

        if (excludeCollection) {
            for (var i = 0; i < mysubcols.length; i++) {
                if (mysubcols[i] == product["@col"])
                    mysubcols.splice(i, 1);
            }
        }

        var sameSubcol = [];
        for (var key in collection) {
            var subcols = collection[key]['@subcol'].split('|');
            for (var i = 0; i < subcols.length; ++i) {
                for (var j = 0; j < mysubcols.length; ++j) {
                    if (subcols[i] == mysubcols[j]) {
                        var sca = collection[key]['@subcat'];
                        nonEmptyCategories[sca] = true;
                        if (sca == subcat && !$.Enumerable.From(sameSubcol).Any(function (t) { return t["@id"] == collection[key]["@id"]; })) {
                            sameSubcol.push(collection[key]);
                        }
                    }
                }
            }
        }

        var numNonEmptyCategories = 0;
        for (var i = 0; i < productCategories.length; ++i) {
            numNonEmptyCategories += nonEmptyCategories[productCategories[i]] ? 1 : 0;
        }
        if (numNonEmptyCategories > 1) {
            productDetail.push('<div class="straps"><h4>' + DictionaryItems.I.Interchangeable + ':</h4>');
        }
        for (var i = 0; i < productCategories.length; ++i) {
            if (numNonEmptyCategories > 1 && nonEmptyCategories[productCategories[i]] /*&& product["@subcat"] != productCategories[i]*/) {
                productDetail.push('<a href="" class="button filter-button watches-change-subcategory ' + ((subcat == productCategories[i]) ? "active" : "") + '" data-subcat="' + productCategories[i] + '"><span class="l">&nbsp;</span><span class="c">' + productCategoryNames[i] + '</span><span class="r">&nbsp;</span></a>');
            }
        }
        if (numNonEmptyCategories > 1) {
            productDetail.push('</div>');
        }

        sameSubcol.sort(function(a, b) { return b["@release"] - a["@release"]});

        productDetail.push('<div class="scroll-wrapper product-variants"><div class="scroll-panel-wrapper num-products-' + ((sameSubcol.length <= 5) ? sameSubcol.length : '5') + '">');
        productDetail.push('<div class="scroll-panel" data-page-size="5"><ul>');
        for (var i = 0; i < sameSubcol.length; i++) {
            var prod = sameSubcol[i];
            var iconUrl = ressourceUrl + prod["@col"] + '/152x79/' + prod["@id"] + '.png';
            productDetail.push('<li class="width-20 item ' + (prod["@id"] == product["@id"] ? "active" : "") + '"><a href="' + window.generateSmartCatalogueUrlForProductID(prod["@id"]) + '" data-itemnumber="' + prod["@id"] + '"><img src="' + iconUrl + '" alt="' + prod["@name"] + '" /></a></li>');
        }

        productDetail.push('</ul></div></div>');

        productDetail.push('</div>');

        return { content: productDetail.join(''), count: sameSubcol.length };
    };

    this.createSubCategoryView = function (product, subcat) {
        var productCategories;
        var productCategoryNames;
        var specificSubcollection = null;
        var excludeCollection = null;
        switch (self.view) {
            case ProductDetailsViewType.WATCH:
                productCategories = ["101", "102", "103"];
                productCategoryNames = [DictionaryItems.WatchRelated.Watch, DictionaryItems.WatchRelated.Bezel, DictionaryItems.WatchRelated.Strap];
                excludeCollection = true;
                break;
            case ProductDetailsViewType.NECKLACE:
                productCategories = ["06", "27", "13"];
                productCategoryNames = [DictionaryItems.N.Necklaces, DictionaryItems.S.SLock, DictionaryItems.C.Clips];
                specificSubcollection = "06";
                break;
            default:
                productCategories = [];
                productCategoryNames = [];
        }
        return createGeneralSubCategoryView(product, productCategories, productCategoryNames, subcat, specificSubcollection, excludeCollection);
    };

    /// <summary>Creates the panel for the product preview zoom</summary>
    this.createPreviewPane = function()
    {
        return '<div id="imgPreview"></div>';
    };

    this.createProductPlaceholder = function()
    {
        return "<li class='placeholder'></li>";
    };

    //JQuery displaying product description
    $(document).ready(function () {
        displayProductDescription();
    });

    function displayProductDescription() {
        $('.product-top-container .description .description-content .read-more')
            .live('click', function () { displayFullDescription(); });
    }
    function displayFullDescription() {
        var applyFullLength = 'full-length';
        var applyBackGroundClass = 'apply-background';
        var parentElement = $('.product-top-container .description');

        parentElement.find('.description-top, .description-content, .description-footer').addClass(applyBackGroundClass)
        parentElement.parent().addClass(applyFullLength);

        $('.product-top-container .description .summary').hide();
        $('.product-top-container .description .full-description').show();

        var descriptionDivHeight = calculateDescriptionDivHeight($('.product-top-container'));

        $('.product-top-container .description .description-content').attr('style', 'height:' + descriptionDivHeight + 'px');

        $('.product-top-container .description').bind('mouseleave', function () { displaySummary(); });
    }
    function displaySummary() {
        var removeBackGroundClass = 'apply-background';
        var removeFullLength = 'full-length';
        var parentElement = $('.product-top-container .description');

        parentElement.find('.description-top, .description-content, .description-footer').removeClass(removeBackGroundClass);
        //parentElement.parent().removeClass(applyFullLength);

        $('.product-top-container .description .summary').show();
        $('.product-top-container .description .full-description').hide();

        $('.product-top-container .description').unbind('mouseleave');

        $('.product-top-container .description .description-content').attr('style', '');
    }
    function calculateDescriptionDivHeight(parentElement) {
        var productTopContainerHeigth = parentElement.outerHeight(true);
        var h2Height = parentElement.find('.info h2').outerHeight(true);
        var materialInfoHeigth = parentElement.find('.material-info').outerHeight(true);
        return productTopContainerHeigth - h2Height - materialInfoHeigth - 23; // -23 for footer (10) and header (10) and some padding on the bottom just to separate from the bottom (3)
    }
}
;/// <summary>Product Model</summary>
function ProductModel(productUrl) {
    var self = this;
    this.name = "CollectionModel";
    this.onLoadEvent = new pandora.utils.Event(this);
    
    $.ajax
    ({
        url: productUrl,
        dataType: "jsonp",
        jsonpCallback: "dataProduct",
        cache: "true",
        success: function(data) {
            self.onLoadEvent.notify({ product: data.data.product });
        }
    });
};    /// <summary>Product controller</summary>
function ProductController(param) {
    var height;
    var self = this;
    var animationDone, dataLoadDone;
    var product; //jquery expression containing the actual product data in xml representation.
    var productView;
    var productVariants;
    var productID = param.productID;
    this.categories = param.categories;
    this.filtersData = param.filtersData;
    this.filterDataSettings = param.filterDataSettings;
    this.setSingleTopViewID = param.setSingleTopViewID;
    this.effectTime = param.effectTime;
    this.container = param.container;
    this.onClose = param.onClose;
    this.onOpenAndFadeinCompleted = param.onOpenAndFadeinCompleted;
    this.adjustContainerHeight = param.adjustContainerHeight;
    this.clickLock = param.clickLock;
    this.productClosedItself = param.productClosedItself;
    this.collection = param.collection;

    this.container.html("<div class='product-pre-load'></div>");
    preloadSpin(".product-pre-load");

    this.getHeight = function () { return height; };

    this.closeProduct = function (callback)
    {
        if (productView)
        {
            productView.fadeOut(function()
                {
                self.onClose(callback);
                $(this).remove();
            });
        }
    };

    var push = function(breadcrumbs, itemNumber) {
        var getCategory = function() {
            var capitaliseFirstLetter = function(string) {
                return string.charAt(0).toUpperCase() + string.slice(1);
            };

            var capitaliseFirstLetters = function(string) {
                var words = string.split(' ');
                return $.Enumerable.From(words).Select(function(t) { return capitaliseFirstLetter(t); }).ToArray().join(' ');
            };

            var title = $.Enumerable
                .From(location.pathname.split('/'))
                .Select('$.replace("-", " ")')
                .Where('$!=""').Select(function(t) { return capitaliseFirstLetters(t); }).ToArray();
            return title.length < 2 ? ['Front Page'] : title.slice(1);
        };

        if (breadcrumbs instanceof Array) {
            breadcrumbs = breadcrumbs.join(' > ');
        }

        var category = getCategory().join(' > ');

        if (category.length > 0) {
            breadcrumbs += ' > ' + category;
        }

        document.netminersPush(function(urlBreadcrumbs) {
            var result = NetminersTracker.GetStandardBreadcrumbs(urlBreadcrumbs, itemNumber);
            return result + ' > ' + breadcrumbs;
        });
    };
    
    var placeholderInitializedAndDataLoaded = function()
    {
        //console.log("placeholderInitializedAndDataLoaded: "+productView+","+productView.length+", "+productView.height());
        self.adjustContainerHeight(productView.outerHeight(true), function()
        {
            productView.fadeIn(self.effectTime, function() { if (self.onOpenAndFadeinCompleted) self.onOpenAndFadeinCompleted(); });
        });
    };

    var onDataLoad = function (sender, args) {
        if (!!args.product) {
            product = args.product;
        }
        if (!!args.productVariants) {
            //console.log("onDataLoad: args.productVariants: "+args.productVariants);
            productVariants = args.productVariants;
        }
        //console.log("TEST: "+product+", "+productVariants);
        if (product != undefined && productVariants != undefined) {
            //console.log("onDataLoad: ok");
            var detailsView = new ProductView(self.filtersData, self.filterDataSettings, self.categories, self.collection);
            var productDetailsView = detailsView.createProductDetail(product, productVariants);
            self.container.html(productDetailsView);

            /* MagicZoom preview */
            var image = $('.product-top-container div.image img', $(self.container));
            image
            .attr('rel', "ZoomImageSrc: '" + image.attr('src').replace('400x400', '1000x1000') + "'")
            .MagicZoom();

            // Preload dimension image 
            $('.spec-img', $(self.container)).each(function () {
                var specimg = $(this);
                var specurl = specimg.attr('imagesrc');

                img = new Image();
                $(img).load(function () {
                    specimg.append('<img src="' + specurl + '" />');
                });
                img.src = specurl;
            });


            if (product["@id"] && (product["@id"] != productID))
                productView = $('#id' + product["@id"]);
            else
                productView = $('#id' + productID);
            if (self.setSingleTopViewID) {
                productView.attr("id", "top-singleview-product");
            }

            var addthis_locale = $("#static-data-init").attr("data-addthis-locale");

            var share_obj = {};

            if ($("#static-data-init").attr("data-addthis-disable-custom-emails") == "false") {

                share_obj = Utils.getAddthisShareVars();

                share_obj["email_vars"]["pan_image_prod_url"] = self.filterDataSettings["imageurl"] + '/' + product["@col"] + '/400x400/' + product["@id"] + '.png';
                var product_name = product["@name"];
                share_obj["email_vars"]["pan_prod_name"] = product_name.toUpperCase();
                share_obj["email_vars"]["pan_prod_number"] = product["@id"];
                if (product["@price"] != "0") {
                    var fmt = Utils.NumberFormatter.getFormatter(self.filterDataSettings["thousandsseparator"], self.filterDataSettings["decimalseparator"]);
                    var from = DictionaryItems.F.From;
                    share_obj["email_vars"]["pan_trans_from"] = from.toUpperCase();
                    share_obj["email_vars"]["pan_prod_price"] = fmt(product["@price"]);
                    share_obj["email_vars"]["pan_trans_currency"] = self.filterDataSettings["currency"];
                }
                else {
                    share_obj["email_vars"]["pan_trans_from"] = '&nbsp;';
                    share_obj["email_vars"]["pan_prod_price"] = '&nbsp;';
                    share_obj["email_vars"]["pan_trans_currency"] = '&nbsp;';
                }

                share_obj["email_vars"]["pan_trans_justsharedalinkwithyou"] = DictionaryItems.J.JustSharedALinkWithYou;
                share_obj["email_vars"]["pan_trans_personalmessageforyou"] = DictionaryItems.P.PersonalMessageForYou;
                share_obj["email_vars"]["pan_trans_no"] = DictionaryItems.N.Number;
                share_obj["email_vars"]["pan_trans_gotowebsite"] = DictionaryItems.G.GoToWebsite;
                share_obj["email_vars"]["pan_trans_findneareststore"] = DictionaryItems.F.FindNearestStore;
                share_obj["email_vars"]["pan_trans_whatisthis"] = DictionaryItems.W.WhatIsThis;
                share_obj["email_vars"]["pan_trans_yourecievedthismailbecause"] = DictionaryItems.Y.YouRecievedThisMailBecause;
                share_obj["email_vars"]["pan_trans_decidedtosenditfromthe"] = DictionaryItems.D.DecidedToSendItFrom;
                share_obj["email_vars"]["pan_trans_visitpandora"] = DictionaryItems.V.VisitPandora;
                share_obj["email_vars"]["pan_trans_wishingyoulotsofunforgettablemoments"] = DictionaryItems.W.WishingYouLotsOfUnforgettableMoments;
                share_obj["email_vars"]["pan_trans_allrightsreserved"] = DictionaryItems.P.PandoraAllRightsReserved;
                share_obj["email_vars"]["pan_trans_pandoraonfacebook"] = DictionaryItems.P.PandoraOnFacebook;
                share_obj["email_vars"]["pan_trans_contactus"] = DictionaryItems.C.ContactUs;
                share_obj["email_vars"]["pan_trans_tersmandconditions"] = DictionaryItems.T.TermsAndConditions;
            }

            $("#share-other-" + product["@id"]).find(".addthis_button_email").attr("addthis:url", product["@url"]);
            $("#share-other-" + product["@id"]).find(".addthis_button_twitter").attr("addthis:url", product["@url"]);
            $("#share-other-" + product["@id"]).find(".addthis_button_facebook").attr("addthis:url", product["@url"]);
            $("#share-other-" + product["@id"]).find(".addthis_button_google_plusone").attr("addthis:url", product["@url"]);

            if (typeof (addthis) == "undefined") {
                $.getScript('http://s7.addthis.com/js/250/addthis_widget.js#username=codehousepandora&async=1', function () {
                    window.addthis.init();
                    window.addthis.toolbox("#share-other-" + product["@id"],
                    { url: product["@url"], data_track_clickback: true, ui_language: addthis_locale, domready: true }, share_obj);
                });
            } else {
                addthis.toolbox("#share-other-" + product["@id"],
                { url: product["@url"], data_track_clickback: true, ui_language: addthis_locale, domready: true }, share_obj);
            }

            productView.hide();


            // Add scroll-menu if product variants exeed 5
            var productVariantz = productView.find(".product-variants");
            if (productVariantz.length > 0) {
                productVariantz.scrollWrap(415);
            }

            // Bind close product
            productView.find(".close a").click(function () {
                if (!self.clickLock.trylock()) {
                    //console.log("ProductController: product close button could not obtain click-lock!");
                }
                self.closeProduct(function () { self.clickLock.unlock(); });
                self.productClosedItself();

                document.location.hash = window.generateSmartCatalogueUrlForProductID(null); ;
                return false;
            });

            //'about' button...
            var defaultViewHeight = productView.outerHeight(true);
            productView.find(".about-btn-container").click(function () {
                if (!self.clickLock.trylock())
                    return false;

                var once = true;
                if (!productView.find('.about-overlay-container').is(":visible")) {
                    var numElements = productView.find(".images, .about-btn-container, .related-item-wrapper, .share, .product-bottom-container").length;
                    productView.find(".images").add(".about-btn-container").add(".related-item-wrapper").add('.share').add('.product-bottom-container').fadeOut('fast', function () {
                        //if (!once) return; once = false;
                        numElements--;
                        if (numElements > 0) return;
                        productView.find('.about-btn-container').html(detailsView.createButton(DictionaryItems.B.Back));
                        //NOTE: Fudge is a additional buffer we add to the height that varies based on browser (IE / others)
                        var fudge = document.all ? 100 : 50;
                        productView.find('.about-overlay-container').css("visibility", "hidden");
                        productView.find('.about-overlay-container').show();
                        var newHeight = productView.outerHeight(true);
                        productView.find('.about-overlay-container').css("visibility", "visible");
                        productView.find('.about-overlay-container').hide();
                        self.adjustContainerHeight(newHeight, function () {
                            productView.find('.about-overlay-container').add('.about-btn-container').fadeIn('fast', self.clickLock.unlock);
                        });
                    }
                    );
                } else {
                    productView.find('.about-overlay-container').add('.about-btn-container').fadeOut('fast', function () {
                        if (!once) return; once = false;
                        productView.find('.about-btn-container').html(detailsView.createButton(DictionaryItems.A.About));
                        self.adjustContainerHeight(defaultViewHeight, function () {
                            productView.find(".images").add(".about-btn-container").add(".related-item-wrapper").add('.share').add('.product-bottom-container').fadeIn('fast', self.clickLock.unlock);
                        });
                    }
                    );
                }
                return false;
            });

            var attachRelatedFunctions = function () {
                productView.find(".related-item-wrapper .related-items ul li a").click(function () {
                    self.showProduct($(this).attr('data-itemnumber'));
                    history.pushState(null, null, $(this).attr('href'));
                    return false;
                });
                productView.find(".related-item-wrapper .related-items .watches-change-subcategory").click(productSubCatChangeClick);
            };

            var productSubCatChangeClick = function () {
                var productSameSub = detailsView.createSubCategoryView(product, $(this).attr('data-subcat'));
                productView.find(".related-item-wrapper .related-items").html(productSameSub.content);
                productView.toggleClass("more-than-zero-variants", productSameSub.count > 0);

                //re-attach listeners:
                attachRelatedFunctions();

                //Apply scrolling if needed
                var productVariants = productView.find(".product-variants");
                if (productVariants.length > 0) {
                    productVariants.scrollWrap(415);
                }

                return false;
            };

            attachRelatedFunctions();

            // Configures the add to wishlist button
            self.configureAddToWishlistButton(productView, productID);

            // Configures the add to my jewellery button
            self.configureAddToMyJewelleryButton(productView, productID);

            productView.find(".create-combine a").click(function () {
                $.get("/Services/ProductInfoService/ProductInfoService.svc/GetDesignerUrl/" + productID, function (data) {
                    window.location = data;
                });
                return false;
            });

            dataLoadDone = true;
            if (animationDone)
                placeholderInitializedAndDataLoaded();

        }
    };

    this.showProduct = function (pID) {
        var getVariants = function(pID) {
            var basis = self.collection[pID]['@baseid'];

            var variants = new Array();

            for (var number in self.collection) {
                if (self.collection[number]['@baseid'] == basis && self.collection[number]['@id'] != pID) {
                    variants.push(self.collection[number]);
                }
            }

            return variants;
        };

        if (productView) {
            productView.fadeOut(function () { /*keep open.*/ });
        }

        productID = pID;
        //console.log("showProduct(): resetting product.");
        product = undefined;
        productVariants = getVariants(pID);

        var details = self.collection[pID];
        onDataLoad(this, { product: details });
    };

    this.onPlaceholderInitDone = function()
    {
        animationDone = true;
        //console.log("onPlaceholderInitDone.");
        if (dataLoadDone)
        {
            placeholderInitializedAndDataLoaded();
        }
    };
    
    /// <summary>Configures the add to my wishlist button. Called by ShowProduct and ChangeProduct</summary>
    this.configureAddToWishlistButton = function (product, currentProductId)
    {

        // The callbackfunction, called when a product has been through the sizeselector
        var onProductsSelectedCallback = function (products)
        {
            $(products).each(function ()
            {
                var productNumber = this.ProductNumber;
                // Persist it to the database
                Wishlist.addToWishlist(productNumber, function ()
                {
                    push(['Wishlist', 'Add to wishlist'], productNumber);

                    // Switch the buttons
                    product.find("div.add-to-wish-list").hide({ effect: "drop", direction: "down", duration: 300, distance: 30 }, function ()
                    {
                        product.find("div.remove-from-wish-list").show({ effect: "drop", direction: "up", duration: 300, distance: 30 });
                    });
                });
            });
        };


        // do some initial formatting of the button here. Product is allready in the wishlist ?
        product.find("div.add-to-wish-list").show();
        Wishlist.isProductInWishlist(currentProductId, function (data)
        {
            if (data == true)
            {
                product.find("div.add-to-wish-list").hide();
                product.find("div.remove-from-wish-list").show();
            }
        });

        //Wishlist event handlers
        product.find("div.add-to-wish-list").click(function ()
        {
            if (User.Authenticated)
            {
                ProductInfoClass.GetProducts(currentProductId, function (productData)
                {
                    ProductSizeSelectorOverlay.Show(productData, false, onProductsSelectedCallback, null, ProductSizeSelectorType.AddToWishlist);
                });
            }
            else
            {
                LoginOverlay.Show(function ()
                {
                    ProductInfoClass.GetProducts(currentProductId, function (productData)
                    {
                        ProductSizeSelectorOverlay.Show(productData, false, onProductsSelectedCallback, null, ProductSizeSelectorType.AddToWishlist);
                    });
                });
            }
            return false;
        });

        product.find("div.remove-from-wish-list").click(function ()
        {
            // Persist it to the database
            Wishlist.removeFromWishlist(currentProductId, true, function ()
            {
                // Switch the buttons
                product.find("div.remove-from-wish-list").hide({ effect: "drop", direction: "down", duration: 300, distance: 30 }, function ()
                {
                    product.find("div.add-to-wish-list").show({ effect: "drop", direction: "up", duration: 300, distance: 30 });
                });
            });
            return false;
        });
    };


    /// <summary>Configures the add to my jewellery button. Called by ShowProduct and ChangeProduct</summary>
    this.configureAddToMyJewelleryButton = function (product, currentProductId)
    {
        var onProductsSelectedCallback = function (products)
        {
            $(products).each(function ()
            {
                var productNumber = this.ProductNumber;

                // Persist it to the database
                JewelryService.Add(productNumber, function ()
                {
                    push(['My Jewelry', 'Add to my jewelry'], productNumber);

                    // Switch the buttons
                    product.find("div.add-to-my-jewellery").hide({ effect: "drop", direction: "down", duration: 300, distance: 30 }, function ()
                    {
                        product.find("div.remove-from-my-jewellery").show({ effect: "drop", direction: "up", duration: 300, distance: 30 });
                    });
                });
            });
        };


        // do some initial formatting of the button here. Product is allready in the jewellery ?
        product.find("div.add-to-my-jewellery").show();
        if (User.Authenticated)
        {
            JewelryService.IsProductInMembersJewellery(currentProductId, function (data)
            {
                if (data == true)
                {
                    product.find("div.add-to-my-jewellery").hide();
                    product.find("div.remove-from-my-jewellery").show();
                }
            });
        }

        product.find("div.add-to-my-jewellery").click(function ()
        {
            if (User.Authenticated)
            {
                ProductInfoClass.GetProducts(currentProductId, function (productData)
                {
                    ProductSizeSelectorOverlay.Show(productData, false, onProductsSelectedCallback, null, ProductSizeSelectorType.AddToMyJewelry);
                });
            }
            else
            {
                LoginOverlay.Show(function ()
                {
                    ProductInfoClass.GetProducts(currentProductId, function (productData)
                    {
                        ProductSizeSelectorOverlay.Show(productData, false, onProductsSelectedCallback, null, ProductSizeSelectorType.AddToMyJewelry);
                    });
                });
            }
            // return false to prevent default behavior of the anchortag
            return false;
        });

        product.find("div.remove-from-my-jewellery").click(function ()
        {
            // Persist it to the database
            JewelryService.Remove(currentProductId, function ()
            {
                // Switch the buttons
                product.find("div.remove-from-my-jewellery").hide({ effect: "drop", direction: "down", duration: 300, distance: 30 }, function ()
                {
                    product.find("div.add-to-my-jewellery").show({ effect: "drop", direction: "up", duration: 300, distance: 30 });
                });
            });
            // return false to prevent default behavior of the anchortag
            return false;
        });


    };


    //show the product!...
    this.showProduct(productID);
}
;/// <summary>A service retrieving product variants data for a specific</summary>
function ProductVariantsService(productId, onDataLoad, oldProductVariants)
{
    var self = this;
    var _log = log4javascriptFactory.getLogger('Product Variants Service');
    _log.trace('Creating Product Categories Service');
    var serviceUrl = '/Services/ProductInfoService/ProductInfoService.svc/GetProductVariants/' + productId;
    this.name = "ProductVariantsService";

    this.onLoadEvent = new pandora.utils.Event(this);

    if (onDataLoad != null)
    {
        this.onLoadEvent.attach(onDataLoad);
    }

    if (!!oldProductVariants)
    {
        for (var i = 0; i < oldProductVariants.length; i++)
        {
            if (oldProductVariants[i]['ProductNo'] == productId)
            {
                self.onLoadEvent.notify({ productVariants: oldProductVariants });
                return;
            }
        }
    }

    //console.log('Starting to retrieve categories data: '+productId);
    $.get(serviceUrl, function (data)
    {
        _log.trace('Categories data retrieved');
        self.onLoadEvent.notify({ productVariants: data });
    });
};/// <summary>A service retrieving country specific data</summary>
function CountriesService() {
    var self = this;
    var _log = log4javascriptFactory.getLogger('Countries Service');
    _log.trace('Creating Countries Service');
    
    this.name = "CountriesService";

    this.onLoadEvent = new pandora.utils.Event(this);

    /// <summary>Gets all product data based on the serviceUrl</summary>
    this.getCountriesData = function (serviceUrl) {
        _log.trace('Starting to retrieve countries data');
        $.get(serviceUrl, function (data) {
            _log.trace('Countries data retrieved');
            self.onLoadEvent.notify({ countriesData: data });
        });
    }
};/// <summary>Handles actions in a collection filter</summary>
function FilterController()
{
    var _log = log4javascriptFactory.getLogger('filter controller');
    _log.setLevel(log4javascript.Level.WARN);
    
    var self = this;
    var filterContainer = $("#filter-container");
    var filter = filterContainer.find(".filter");
    var filterTopContainer = filter.find(".filter-tabs");
    var filterTop = filterTopContainer.find(".filter-param");
    var filterInitParams = filter.find("#filter-init li");
    var normalOptionsFilters = filter.find("#normal-options-menu");
    var extendedOptionsFilters = filter.find("#more-options-menu");
    var moreOptionsButton = filter.find("#more-options a");
    var defaultSortOrder = $("#sortby-dropdown-1" + " select").attr("data-filter-sort-mode");

    // If the dropdown only has 1 item (sort by...), the the dropdown should be hidden
    if ($("#sortby-dropdown-1 select option").length == 1) {
        $("#sortby-dropdown-1").hide();
    }

    var dropDowns = [];

    var jumpToTop = function() {
        window.scrollTo(0, filterContainer.offset().top - 43); //43: one less than the height of the top menu, to counter bug in old IE.
    };

    this.onFilterChanged = new pandora.utils.Event(this);
    this.onSortOrderChanged = new pandora.utils.Event(this);
    var sortDrop1 = new DropdownComponent2("#sortby-dropdown-1", [ "data-filter-sort-mode" ]);
    sortDrop1.clickedEvent.attach(function(c, arg) { self.onSortOrderChanged.notify({ sortOrder: arg["data-filter-sort-mode"] }); jumpToTop(); }); //just chain the event.
    dropDowns.push(sortDrop1);
    var sortDrop2 = new DropdownComponent("#sortby-dropdown-2", [ "data-filter-sort-mode" ]);
    sortDrop2.clickedEvent.attach(function(c, arg) { self.onSortOrderChanged.notify({ sortOrder: arg["data-filter-sort-mode"] }); jumpToTop(); }); //just chain the event.
    dropDowns.push(sortDrop2);

    var resetButton = $(".filter-reset-button");
    var state = 'normal'; //Can assume normal or extended
    var activeClass = 'active';
    
    var filterTabs = new FilterImplementation();
    var listFilters = new FilterImplementation();
    var userNormalFilters = new FilterImplementation();
    var userExtendedFilters = new FilterImplementation();

    //Show filter
    filter.fadeIn(250);

    // modify '.default-reset' filters to match ALL filters
    $('.filter-button.default-reset').each(function () {
        var filter = $(this);
        var siblingsFilters = filter.parent().siblings().find('.filter-button').toArray();

        if (siblingsFilters.length > 0) {
            var combinedFilter = $.Enumerable.From(siblingsFilters).SelectMany(function (t) {
                return $(t).attr('data-filter-value').split('|');
            }).Distinct().Concat().ToArray().join('|');
            var dimension = $(siblingsFilters[0]).attr('data-filter-field');
            var operator = $(siblingsFilters[0]).attr('data-filter-compare');

            filter.attr({
                'data-filter-field': dimension,
                'data-filter-compare': operator,
                'data-filter-value': combinedFilter
            });
        }
    })

    var getMatchedPageFilters = function (urlFilter) {
        return $.Enumerable.From($('.filter-button'))
            .Where(function (t) { return $(t).attr('data-filter-field') == urlFilter.dimension; }) // same dimension
            .Where(function (t) { return $(t).attr('data-filter-compare') == urlFilter.operator; }) // same comparison operator
            .Where(function (t) {
                var pageFilterValues = $.Enumerable.From($(t).attr('data-filter-value').split('|'));
                var urlFilterValues = $.Enumerable.From(urlFilter.value.split('|'));

                return pageFilterValues.Count() == urlFilterValues.Count() 
                    && pageFilterValues.All(function(f) { return urlFilterValues.Contains(f); });
            });
    };

    /// <summary>Sets up the visual elements in the filter menu based on the current state of the filters</summary>
    var setupFilterView = function () {
        var filters = state == 'normal' ? userNormalFilters : userExtendedFilters;
        filters.clear();

        // get filters from smart url
        var smartCatalogueLink = window.SmartCatalogueLinks.parseHash(document.location.hash || '#');

        for (i = 0; i < smartCatalogueLink.filters.length; i++) {
            var matchedPageFilters = getMatchedPageFilters(smartCatalogueLink.filters[i]);
            matchedPageFilters.ForEach(function (pageFilter) {
                _log.debug('url filter matched page filter', smartCatalogueLink.filters[i]);
                
                $(pageFilter).closest('.filter-param').find('.filter-button').attr('data-filter-default', 'false').removeClass('active');
                $(pageFilter).attr('data-filter-default', 'true').addClass('active');

                filters.createFilter(smartCatalogueLink.filters[i].dimension, smartCatalogueLink.filters[i].value, smartCatalogueLink.filters[i].operator);
            });
        }

        if (state == 'normal') {
            $("div.more-options").removeClass(activeClass);
            normalOptionsFilters.addClass(activeClass);
            extendedOptionsFilters.removeClass(activeClass);
            moreOptionsButton.parent().removeClass(activeClass);
            filterContainer.height(parseInt(filterTopContainer.outerHeight(true)) + parseInt(normalOptionsFilters.outerHeight(true)));
            var defFilter = filterTopContainer.find(".filter-param .filter-button[data-filter-default=true]").addClass(activeClass);
            //var defFilter = filterTopContainer.find(".filter-param .filter-button[data-filter-default=true]");
            if (defFilter.length == 1 && !defFilter.hasClass('default-reset'))
                filterTabs.createFilter(defFilter.attr("data-filter-field"), defFilter.attr("data-filter-value"), defFilter.attr("data-filter-compare"));
        } else {
            $("div.more-options").addClass(activeClass);
            extendedOptionsFilters.addClass(activeClass);
            normalOptionsFilters.removeClass(activeClass);
            moreOptionsButton.parent().addClass(activeClass);
            filterContainer.height(parseInt(filterTopContainer.outerHeight(true)) + parseInt(extendedOptionsFilters.outerHeight(true)));
            filterTop.find(activeClass).removeClass(activeClass);
            var filterNameWidth = 0;
            var filterNames = extendedOptionsFilters.find(".filter-select span.c span.text");
            filterNames.each(function () {
                if ($(this).width() > filterNameWidth)
                    filterNameWidth = $(this).width();
            });
            filterNames.width(filterNameWidth);
        }

        for (var i = 0; i < dropDowns.length; ++i) {
            dropDowns[i].resetSelection();
        }
    };

    this.getFilterHeight = function() {
        return parseInt(filterTopContainer.outerHeight(true)) + (state == 'normal') ? parseInt(normalOptionsFilters.outerHeight(true)) : parseInt(extendedOptionsFilters.outerHeight(true));
    };

    this.getFilters = function() {
        return new FilterImplementation(filterTabs, listFilters, (state == 'normal') ? userNormalFilters : userExtendedFilters);
    };

    this.getVisibleFilters = function () {
        var result = [];
        var filters = this.getFilters().getFilters();

        for (var i = 0; i < filters.length; i++) {
            var matchedPageFilters = getMatchedPageFilters({ dimension: filters[i].field, operator: filters[i].operator, value: filters[i].value });

            if (matchedPageFilters.Any()) {
                result.push(filters[i]);
            }
        }

        return result;
    }

    this.getDefaultSortOrder = function() {
        return defaultSortOrder;
    };

    this.getSmartCatalogueLink = function () {
        var filters = state == 'normal' ? userNormalFilters : userExtendedFilters;
        return window.SmartCatalogueLinks.buildHash(filters.get());
    }
    
    /// <summary>Changes the state of the filter view</summary>
    this.toggleFilters = function() {
        if (state == 'normal')
            state = 'extended';
        else
            state = 'normal';

        setupFilterView();
    };

    /// <summary>Initiates handler for more options buttons</summary>
    moreOptionsButton.unbind('click').click(function () {
        self.toggleFilters();
        return false;
    });
    
    /// <summary>Resets filters etc</summary>
    resetButton.unbind('click').click(function () {
        userNormalFilters.clear();
        userExtendedFilters.clear();
        normalOptionsFilters.find("ul.filter-param a.filter-button").removeClass(activeClass);

        for (var i = 0; i < dropDowns.length; ++i) {
            dropDowns[i].resetSelection();
        }
        
        self.onFilterChanged.notify({ listFilters: listFilters.get(), environmentFilters: (state == 'normal') ? userNormalFilters.get() : userExtendedFilters.get(), sortOrder: null });
        return false;
    });
    /// <summary>Retrieves initial filtering parameters from page</summary>
    filterInitParams.each(function (index) {
        try {
        listFilters.createFilter($(this).attr("data-filter-field"), $(this).attr("data-filter-value"), $(this).attr("data-filter-compare"));
        } catch(e) {}
    });
    
    var filterParamRadio     = new RadioComponent(".filter-param");
    filterParamRadio.clickedEvent.attach(function (c, arg) {
        _log.trace('filter radio button clicked');
        if (arg.down) {
            if (arg.clicked.closest('.filter-tabs').length == 1) {
                if (arg.clicked.hasClass('default-reset')) {
                    filterTabs.clear();
                    _log.trace('filter tabs cleared');
                    var filters = state == 'normal' ? userNormalFilters : userExtendedFilters;
                    filters.createFilter(arg.clicked.attr("data-filter-field"), arg.clicked.attr("data-filter-value"), arg.clicked.attr("data-filter-compare"));
                } else {
                    filterTabs.createFilter(arg.clicked.attr("data-filter-field"), arg.clicked.attr("data-filter-value"), arg.clicked.attr("data-filter-compare"));
                    _log.trace('filter tabs created');
                }
            } else {
                var f = (state == 'normal') ? userNormalFilters : userExtendedFilters;
                f.createFilter(arg.clicked.attr("data-filter-field"), arg.clicked.attr("data-filter-value"), arg.clicked.attr("data-filter-compare"));
                _log.trace('filter created');
            }
        } else {
            var f = (state == 'normal') ? userNormalFilters : userExtendedFilters;
            f.removeFilter(arg.clicked.attr("data-filter-field"));
            _log.trace('filter removed');
        }
        var t = filterTabs.get();
        var z = listFilters.get();
        self.onFilterChanged.notify({ listFilters: listFilters.get(), environmentFilters: (state == 'normal') ? userNormalFilters.get() : userExtendedFilters.get() });
        jumpToTop();
    });

    var filterCollectionDrop = new DropdownComponent("#filter-collections-dropdown", [ "data-filter-value", "data-filter-field", "data-filter-compare" ]);
    filterCollectionDrop.clickedEvent.attach(function(c, arg) {
        userExtendedFilters.createFilter(arg["data-filter-field"], arg["data-filter-value"], arg["data-filter-compare"]);
        self.onFilterChanged.notify({ listFilters: listFilters.get(), environmentFilters: (state == 'normal') ? userNormalFilters.get() : userExtendedFilters.get() });
    });
    dropDowns.push(filterCollectionDrop);

    var filterCatDrop = new DropdownComponent("#filter-categories-dropdown", [ "data-filter-value", "data-filter-field", "data-filter-compare" ]);
    filterCatDrop.clickedEvent.attach(function(c, arg) {
        userExtendedFilters.createFilter(arg["data-filter-field"], arg["data-filter-value"], arg["data-filter-compare"]);
        self.onFilterChanged.notify({ listFilters: listFilters.get(), environmentFilters: (state == 'normal') ? userNormalFilters.get() : userExtendedFilters.get() });
    });
    dropDowns.push(filterCatDrop);

    var filterMetalDrop = new DropdownComponent("#filter-metal-dropdown", [ "data-filter-value", "data-filter-field", "data-filter-compare" ]);
    filterMetalDrop.clickedEvent.attach(function(c, arg) {
        userExtendedFilters.createFilter(arg["data-filter-field"], arg["data-filter-value"], arg["data-filter-compare"]);
        self.onFilterChanged.notify({ listFilters: listFilters.get(), environmentFilters: (state == 'normal') ? userNormalFilters.get() : userExtendedFilters.get() });
    });
    dropDowns.push(filterMetalDrop);

    var filterStonesDrop = new DropdownComponent("#filter-stones-dropdown", [ "data-filter-value", "data-filter-field", "data-filter-compare" ]);
    filterStonesDrop.clickedEvent.attach(function(c, arg) {
        userExtendedFilters.createFilter(arg["data-filter-field"], arg["data-filter-value"], arg["data-filter-compare"]);
        self.onFilterChanged.notify({ listFilters: listFilters.get(), environmentFilters: (state == 'normal') ? userNormalFilters.get() : userExtendedFilters.get() });
    });
    dropDowns.push(filterStonesDrop);

    var filterMaterialDrop = new DropdownComponent("#filter-materials-dropdown", [ "data-filter-value", "data-filter-field", "data-filter-compare" ]);
    filterMaterialDrop.clickedEvent.attach(function(c, arg) {
        userExtendedFilters.createFilter(arg["data-filter-field"], arg["data-filter-value"], arg["data-filter-compare"]);
        self.onFilterChanged.notify({ listFilters: listFilters.get(), environmentFilters: (state == 'normal') ? userNormalFilters.get() : userExtendedFilters.get() });
    });
    dropDowns.push(filterMaterialDrop);

    var filterColorDrop = new DropdownComponent("#filter-colors-dropdown", [ "data-filter-value", "data-filter-field", "data-filter-compare" ]);
    filterColorDrop.clickedEvent.attach(function(c, arg) {
        userExtendedFilters.createFilter(arg["data-filter-field"], arg["data-filter-value"], arg["data-filter-compare"]);
        self.onFilterChanged.notify({ listFilters: listFilters.get(), environmentFilters: (state == 'normal') ? userNormalFilters.get() : userExtendedFilters.get() });
    });
    dropDowns.push(filterColorDrop);

    setupFilterView();

    window.setTimeout("alignFilterParam()", 50);
//    var alignFilterParam = function() {
//        var filterparam = $("#filter-container .filter-tabs ul");
//        filterparam.css("margin-left", -(filterparam.width() / 2));
//    }
//    alignFilterParam();
}

/////////////////////////
function DropdownComponent2(tagid, optionAttrs) {
    var activeClass = 'active';
    //    var button = $(tagid + " .select-button-drop");
    var dropdown = $(tagid + " .PandoraDropDown").removeClass(activeClass);
    var options = $(tagid + " .select-dropdown ul li a");
    var text = dropdown.find(".text");
    var defaultText = text.text();

    if ($(tagid).attr('defaultText') == null) {
        $(tagid).attr('defaultText', defaultText);
    }
    else {
        defaultText = $(tagid).attr('defaultText');
    }

    var self = this;
    this.clickedEvent = new pandora.utils.Event(this);

    dropdown.unbind('change').change(function (e) {
        e.preventDefault();
        var event = {};
        var dropDown = $("*[id$='filterDropDown']");

        var option = $(dropDown).find("option:selected");

        if (option.val() != 'none') {
            $(dropDown).find("option[data-filter-sort-mode='none']").remove();
        }

        for (var i = 0; i < optionAttrs.length; ++i)
            event[optionAttrs[i]] = option.attr(optionAttrs[i]);
        self.clickedEvent.notify(event);

        text.text(option.text());
    });

    this.resetSelection = function () {
        var dropDown = $("*[id$='filterDropDown']");

        if ($(dropDown).find("option[data-filter-sort-mode=none]").length == 0) {
            dropDown.prepend("<option data-filter-sort-mode='none' value='none'>" + defaultText + "</option>");
        }
        dropDown.find("option:first-child").attr("selected", "selected");

        var updateClass = "ddl-sortby";
        dropDown.parent().find("a.button.PandoraDropDown." + updateClass).remove();
        $("div.options.PandoraDropDown." + updateClass).remove();
        $("select.PandoraDropDown." + updateClass).removeClass("styled");
        $("select.PandoraDropDown." + updateClass).styleSelect();
    };
}

/////////////////////////
function DropdownComponent(tagid, optionAttrs) {
    var activeClass = 'active';
    var button = $(tagid + " .select-button-drop").removeClass(activeClass);
    var dropdown = $(tagid + " .select-dropdown");
    var options = $(tagid + " .select-dropdown ul li a");
    var text = button.find(".text");

    var defaultText = text.text();

    if ($(tagid).attr('defaultText') == null) {
        $(tagid).attr('defaultText', defaultText);
    }
    else {
        defaultText = $(tagid).attr('defaultText');
    }

    text.empty().text(defaultText);

    var self = this;
    this.clickedEvent = new pandora.utils.Event(this);

    var dropclick = function(e) {
        e.preventDefault();
        $(".select-dropdown").slideUp(200); //cancel all other dropdowns.
        $(document).unbind('mousedown', dropclick);
        var option = $(e.target).closest(".select-dropdown ul li a");
        if(option.length == 1) {
            options.removeClass(activeClass);
            option.addClass(activeClass);
            var event = {};
            for(var i = 0; i < optionAttrs.length; ++i)
                event[optionAttrs[i]] = option.attr(optionAttrs[i]);
            self.clickedEvent.notify(event);

            text.text(option.text());
        }
        
        return false;
    }

    button.unbind('click').bind("click", function (e) {
        e.preventDefault();
        if(!dropdown.is(":visible")) {
            dropdown.slideDown(200);
            dropdown.css("z-index", 900000);
            $(document).bind('mousedown', dropclick);
        } else {
        }
        return false;
    });

    this.resetSelection = function() {
        options.removeClass(activeClass);
        text.text(defaultText);
    }
}


function RadioComponent(tagid) {
    var activeClass = 'active';
    var buttons = $(tagid + " li a").removeClass(activeClass);
    
    var self = this;
    this.tagid = tagid;
    this.buttons = buttons;
    this.clickedEvent = new pandora.utils.Event(this);

    buttons.unbind('click').bind("click", function (e) {
        e.preventDefault();
        if ($(this).hasClass(activeClass)) {
            if($(this).closest('.filter-tabs').length==1) {
                return false;
            }
            $(this).removeClass(activeClass);
            self.clickedEvent.notify({ clicked: $(this), down: false });
        } else {
            $(this).parent().parent().find("a." + activeClass).removeClass(activeClass);
            $(this).addClass(activeClass);
            self.clickedEvent.notify({ clicked: $(this), down: true });
        }
        return false;
    });
}

function alignFilterParam() {
    var filterparam = $("#filter-container .filter-tabs ul");
    filterparam.css("margin-left", -(filterparam.width() / 2));
};/*

*/
(function ($)
{
    $.fn.MagicZoom = function (options)
    {
        this.each(function ()
        {
            $(this).load(function ()
            {
                var opts;
                eval('var a = {' + $(this).attr('rel') + '}');
                opts = $.extend({}, $.fn.MagicZoom.defaults, options);
                opts = $.extend({}, opts, a);
                $(this).data('zoom', new MagicZoom($(this), opts));
            });

        });
        return this;
    };

    $.fn.MagicZoom.defaults =
	{
	    ZoomImageSrc: '',
	    lensOpacity: 0.5,
	    smoothMove: 3,
	    cursorHeight: 170,
	    cursorWidth: 170
	};


    function MagicZoom(img, opts)
    {
        var sImg = $(img);
        var zoomImage = null;
        var zoomImageLoaded = false;
        var $mouseTrap = null;
        var cursor = null;
        var controlTimer = 0;
        var mx, my;
        var ctx = this;
        var zw;

        // Removes cursor, blur layer etc.
        this.removeCursor = function ()
        {
            if (cursor)
            {
                cursor.remove();
                cursor = null;
            }
        };

        this.destroy = function ()
        {
            sImg.data('zoom', null);

            if ($mouseTrap)
            {
                $mouseTrap.unbind();
                $mouseTrap.remove();
                $mouseTrap = null;
            }
            this.removeCursor();
        };


        this.controlLoop = function ()
        {
            var offsetx = (mx - sImg.offset().left) >> 0;
            var offsety = (my - sImg.offset().top) >> 0;
            var x = offsetx - (opts.cursorWidth * 0.5);
            var y = offsety - (opts.cursorHeight * 0.5);

            if (x < 0)
                x = 0;
            else if (x > (sImg.outerWidth() - opts.cursorWidth))
                x = (sImg.outerWidth() - opts.cursorWidth);
            if (y < 0)
                y = 0;
            else if (y > (sImg.outerHeight() - opts.cursorHeight))
                y = (sImg.outerHeight() - opts.cursorHeight);

            if (cursor == null)
            {
                cursor = ctx.createCursor(sImg.parent()).show();
            }

            cursor.css({ left: x, top: y });

            // calculate background position
            var bgX = x;
            var bgY = y;
            if (zoomImageLoaded)
            {
                bgX = (offsetx * zoomImage.width / sImg.outerWidth() - (opts.cursorWidth * 0.5)) >> 0;
                bgY = (offsety * zoomImage.height / sImg.outerHeight() - (opts.cursorHeight * 0.5)) >> 0;
            }
            cursor.css('background-position', (-bgX) + 'px ' + (-bgY) + 'px');

            controlTimer = setTimeout(function () { ctx.controlLoop(); }, 30);
        };

        this.createImage = function (imgSrc, callback)
        {
            img = new Image();
            $(img).load(callback);
            img.src = imgSrc;

            return img;
        };

        this.createCursor = function (container)
        {
            var lens = $("<div class='zoom-magic-lens' />")
						.css({ 'width': opts.cursorWidth + 'px', 'height': opts.cursorHeight + 'px' });
            if (zoomImageLoaded)
            {
                lens.css({ 'background-image': "url('" + zoomImage.src + "')" });
            }
            else
            {
                zoomImage = this.createImage(opts.ZoomImageSrc, function () { zoomImageLoaded = true; ctx.removeCursor(); });
                lens.css({ 'opacity': opts.lensOpacity })
                .append($('<img src="http://assets.pandora.net/design/consumer/images/ajax-loader.gif"/>')
                            .css(
                            {
                                position: 'absolute',
                                width: '15px',
                                height: '15px',
                                left: ((opts.cursorWidth * 0.5) - 8) >> 0,
                                top: ((opts.cursorHeight * 0.5) - 8) >> 0
                            })
                );

            }

            container.append(lens);
            // return cursor
            return lens;
        }

        /* Init function start.  */
        this.Init = function ()
        {
            $mouseTrap = sImg
				.parent()
				.append(
					$('<div class="mousetrap" style="z-index:999;position:absolute;" />')
						//.css('background-image', 'url(".")')
						.css('width', sImg.outerWidth() + 'px')
						.css('height', sImg.outerHeight() + 'px')
						.css('left', '0px')
						.css('top', '0px')
				)
				.find(':last');

            $mouseTrap
				.bind('mousemove', this, function (event)
				{
				    mx = event.pageX;
				    my = event.pageY;
				})
				.bind('mouseleave', this, function (event)
				{
				    clearTimeout(controlTimer);
				    ctx.removeCursor();

				    return false;
				})
				.bind('mouseenter', this, function (event)
				{
				    var container = sImg.parent();
				    mx = event.pageX;
				    my = event.pageY;
				    zw = event.data;

				    ctx.removeCursor();
				    cursor = ctx.createCursor(container);
				    cursor.fadeIn(0);

				    $mouseTrap.css('cursor', cursor.css('cursor'));

				    // Start processing. 
				    zw.controlLoop();

				    // Don't return false here otherwise opera will not detect change of the mouse pointer type.
				    return;
				})
                .css('background-color', 'silver')
                .fadeTo('fast', 0.0001);
        };

        ctx.Init();
    }


})(jQuery);